前言

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

因此,接收者还是先接收缓冲区数据,再接收发送者的数据。其实就是按队列的先进先出顺序。

总结

留下两个问题:

发送者分别遇到无有休眠接收协程,有休眠接收协程,无接收协程且缓冲区没满,缓冲区满了四种情况该如何处理?

接收者分别遇到无休眠发送协程且缓冲区为空,无发送协程且缓冲区有数据,有休眠发送协程且缓冲区已满,缓冲区满了四种情况该如何处理?