本文是《Go专家编程》的阅读笔记

1. channel数据结构

从src/runtime/chan.go 中查看channel源码

  1. type hchan struct {
  2. qcount uint // 当前队列剩余的元素个数
  3. dataqsiz uint // 环形队列长度,即环形队列可存放的元素个数
  4. buf unsafe.Pointer // 环形队列指针
  5. elemsize uint16 // 元素类型大小
  6. closed uint32 // channel关闭标识
  7. elemtype *_type // 元素类型
  8. sendx uint // 入队下标,指示元素写入时在队列的存放位置
  9. recvx uint // 出队下标,指示队列元素的读取位置
  10. recvq waitq // 等待读取消息的goroutine队列
  11. sendq waitq // 等待写入消息的goroutine队列
  12. lock mutex // 互斥锁,保证并发安全
  13. }

3. channel读写

3.2 向channel写数据

channel发送过程.png

写入过程做了以下事情:

  • 判断是否有等待接收的goroutine;有,从recvq中取出一个G,将要发送的数据写入G,然后唤醒G,结束发送。
  • recvq为空,判断缓冲区中是否有空位;有,将要发送的数据写入到缓冲区,结束发送。
  • 缓冲区中没有空位,将当前goroutine加入到sendq队列中,等待唤醒;被唤醒,说明数据已被取走,结束发送。

3.2 向channel读数据

channel接收过程.png

读取过程做了以下事情:

  • 判断有无等待发送的goroutine;有,判断有无缓冲区;有,从缓冲区取出一个元素,从sendq中取出一个G,将G中数据写入到缓冲区,唤醒G,结束接收。
  • 无缓冲区,从sendq中取出一个G,取出G中的数据,唤醒G,结束接收。
  • sendq为空,判断缓冲区中是否还有元素;有,从缓冲区取出一个元素,结束接收。
  • 缓冲区无元素,将当前goroutine加入到recvq队列中,等待唤醒;被唤醒,说明数据已写入,结束接收。

channel读取过程的疑问解决:

在有缓冲区的情况下,从buf队首取出一个元素,从sendq取出一个G,G中的数据写入buf队尾,此时G中没有数据,需要唤醒让它结束,实际数据我们是从buf中获取的。