本文是《Go专家编程》的阅读笔记
1. channel数据结构
从src/runtime/chan.go 中查看channel源码
type hchan struct {
qcount uint // 当前队列剩余的元素个数
dataqsiz uint // 环形队列长度,即环形队列可存放的元素个数
buf unsafe.Pointer // 环形队列指针
elemsize uint16 // 元素类型大小
closed uint32 // channel关闭标识
elemtype *_type // 元素类型
sendx uint // 入队下标,指示元素写入时在队列的存放位置
recvx uint // 出队下标,指示队列元素的读取位置
recvq waitq // 等待读取消息的goroutine队列
sendq waitq // 等待写入消息的goroutine队列
lock mutex // 互斥锁,保证并发安全
}
3. channel读写
3.2 向channel写数据
写入过程做了以下事情:
- 判断是否有等待接收的goroutine;有,从recvq中取出一个G,将要发送的数据写入G,然后唤醒G,结束发送。
- recvq为空,判断缓冲区中是否有空位;有,将要发送的数据写入到缓冲区,结束发送。
- 缓冲区中没有空位,将当前goroutine加入到sendq队列中,等待唤醒;被唤醒,说明数据已被取走,结束发送。
3.2 向channel读数据
读取过程做了以下事情:
- 判断有无等待发送的goroutine;有,判断有无缓冲区;有,从缓冲区取出一个元素,从sendq中取出一个G,将G中数据写入到缓冲区,唤醒G,结束接收。
- 无缓冲区,从sendq中取出一个G,取出G中的数据,唤醒G,结束接收。
- sendq为空,判断缓冲区中是否还有元素;有,从缓冲区取出一个元素,结束接收。
- 缓冲区无元素,将当前goroutine加入到recvq队列中,等待唤醒;被唤醒,说明数据已写入,结束接收。
channel读取过程的疑问解决:
在有缓冲区的情况下,从buf队首取出一个元素,从sendq取出一个G,G中的数据写入buf队尾,此时G中没有数据,需要唤醒让它结束,实际数据我们是从buf中获取的。