channel原理分析
数据结构
type hchan struct {
qcount uint // 当前队列中剩余的元素个数
dataqsiz uint // 环形队列的长度,即为缓冲区的大小
buf unsafe.Pointer // 指向环形队列的指针
elemsize uint16 // 管道中元素的大小
closed uint32 // 管道是否关闭
elemtype *_type // 元素的类型
sendx uint // 元素写入管道时存放到队列中的下标位置,队尾
recvx uint // 指定下一个从管道中读取元素在队列中的位置,队首
recvq waitq // 等待读消息的协程队列
sendq waitq // 等待写消息的协程队列
lock mutex // 互斥锁,chan不允许并发读写
}
从管道读数据时,如果缓冲区为空或者没有缓冲区,则当前协程会被阻塞,并加入recvq队列。
向管道写入数据时,如果缓冲区已满或者没有缓冲区,则当前协程会被阻塞,并加入sendq队列。
一般情况recvq和sendq至少有一个为空,除了同一个协程使用select向管道一边写数据,一边读数据,此时协程分别位于两个等待队列中。
协程读取管道时的阻塞条件:
- 管道无缓冲区
- 管道缓冲区无数据,协程会加入recvq队列
- 管道的值为nil
协程写入管道时的阻塞条件:
- 管道无缓冲区
- 管道缓冲区已满,协程会加入sendq队列
- 管道的值为nil
关闭管道需要注意的点:
- 唤醒recvq中的协程获取的数据都为nil
- 唤醒sendq中的协程并且触发panic
以下操作会触发panic:
关闭nil的管道
关闭已经关闭的管道
向关闭的管道写入数据
select的case在读管道时不会阻塞,读不到数据直接返回。
for-range读取管道中的数据时,当管道中没有数据则会阻塞。