初次使用golang channel
goroutine之间的同步
goroutine是golang中在语言级别实现的轻量级线程,仅仅利用go就能立刻起一个新线程。多线程会引入线程之间的同步问题,经典的同步问题如生产者-消费者问题,在c,java级别需要使用锁、信号量进行共享资源的互斥使用和多线程之间的时序控制,而在golang中有channel作为同步的工具。
1. channel实现两个goroutine之间的通信
1 | package main |
- 创建一个channel,
make(chan TYPE {, NUM})
, TYPE指的是channel中传输的数据类型,第二个参数是可选的,指的是channel的容量大小。 - 向channel传入数据,
CHAN <- DATA
, CHAN指的是目的channel即收集数据的一方,DATA则是要传的数据。 - 启动一个goroutine接收main routine向channel发送的数据,
go func(){ BODY }()
新建一个线程运行一个匿名函数。 - 从channel读取数据,
DATA := <-CHAN
,和向channel传入数据相反,在数据输送箭头的右侧的是channel,形象地展现了数据从‘隧道’流出到变量里。 - 通知主线程任务执行结束,doneChan的作用是为了让main routine等待这个刚起的goroutine结束,这里显示了channel的另一个特性,如果从empty channel中读取数据,则会阻塞当前routine,直到有数据可以读取。
上面这个程序就是main routine向另一个routine发送了3条int类型的数据,当3条数据被接收到后,主线程也从阻塞状态恢复运行,随后结束。
2. 不要陷入“死锁”
我一开始用channel的时候有报过*”fatal error: all goroutines are asleep - deadlock! “*的错误,真实的代码是下面这样的:
1 | package main |
我的本意是从main routine发送给另一个routine一个int型的数据,但是运行出了上述的错误,原因有2个:
- 当前routine向channel发送/接收数据时,如果另一端没有相应地接收/发送,那么当前routine则会进行休眠。
- 这个程序的main routine先行在
ch <- 1
进入休眠状态,程序的余下部分根本来不及运行,那么channel里的数据永远不会被读出,也就不能唤醒main routine,进入“死锁”。
解决这个“死锁”的方法可是是设置channel的容量大小大于1,那么channel就不会因为数据输入而阻塞主程; 或者将数据输入channel的语句置于启动新的goroutine之后。
3. channel作为状态转移的信号源
我跟着MIT的分布式计算课程做了原型为Map-Reduce的课后练习,目的是实现一个Master向Worker分派任务的功能:Master服务器去等待Worker服务器连接注册,Master先将Map任务和Reduce任务分派给这些注册Worker,等待Map任务全部完成,然后将Reduce任务再分配出去,等待全部完成。
1 | // Initialize a channel which records the process of the map jobs. |
以上是我截取自己写的代码,关于用channel来传递当前Map任务的进度信息,用类似信号的方式标注当前的任务执行状态。
- 当从channel中读取到”start, {NUM}”时找一个空闲的Worker去执行Map任务,并且等待它的完成,完成成功则向channel中发送”end, {NUM}”信号,表明任务完成,如果失败,就重发”start, {NUM}”信号。
- 从channel中读取到”end, {NUM}”时,把剩余任务数减1。
这种信号触发的方式,触发Master的状态转移,并且可以通过增加信号以及信号处理的方式,拓展业务处理的情况,暂时还能处理这个需求情景。