前言
Go推荐通过通信来共享内存,而channel就实现了这一理念。那channel是怎么运行的呢?
功能
举个例子看下channel的使用效果:
以上代码新建了一个缓冲区为8的管道,然后开启read和五个write读写协程。写协程写入一个随机数,读协程每隔一秒读取并打印,效果如下:
说明协程间可以通过管道来互相通信。接着了解下channel的结构。
channel结构
GOROOT/src/runtime/chan.go
其中发送者和接收者队列是一个waitq类型,具体如下:
firstlastsudog
也就是说,waitq是一个列表队列,队列里每个元素都是一个sudog结构体,sudog中包装着一个协程。
hchan
- 头部
这部分表示一个环型缓冲区。图解如下:
- 尾部
chan <- <- chan
结合示例代码。运行结构如下:
由于写协程一直写,读协程每隔一秒才读一次,因此很快将缓冲区写满了,这时:
- 写协程被装入sudog进行休眠等待
- 读协程每隔一秒从缓冲区读取数据
运行原理
chan <-
- 先查看是否有接收者,有则优先唤醒并拷贝数据给接收者,然后结束
- 无接收者再查看缓冲区,数据未满则将数据放入缓冲区,然后结束
- 缓冲区也满了,则封装成sudog,休眠等待
<- chan
- 优先接收缓冲区的值
- 再接收发送者的值
- 否则休眠等待
思考下:
有休眠的接收者,且缓冲区数据已满的情况是否存在?为什么?
有休眠的发送者,且缓冲区为空的情况是否存在?为什么?
以上答案:
有休眠的接收者,缓冲区不会出现数据已满情况。因为接收者要休眠,得缓冲区没数据才行。
有休眠的发送者,缓冲区不会出现为空情况。因为发送者要休眠,得缓冲区数据已满才行。
源码分析
chan <-GOROOT\src\runtime\chan.gochansend1
chansend
<- chanGOROOT\src\runtime\chan.gochanrecv1
chanrecv
recv
因此,接收者还是先接收缓冲区数据,再接收发送者的数据。其实就是按队列的先进先出顺序。
总结
留下两个问题:
发送者分别遇到无有休眠接收协程,有休眠接收协程,无接收协程且缓冲区没满,缓冲区满了四种情况该如何处理?
接收者分别遇到无休眠发送协程且缓冲区为空,无发送协程且缓冲区有数据,有休眠发送协程且缓冲区已满,缓冲区满了四种情况该如何处理?