Go SDK/src/runtime/chan.go
type hchan struct {
qcount uint // 环形队列中剩余元素个数
dataqsiz uint // 环形队列长度,make函数指定的缓冲区容量
buf unsafe.Pointer // 指向环形队列的指针
elemsize uint16 // 队列内每个元素的大小
closed uint32 // 通道的关闭标识位
elemtype *_type // 队列内元素类型
sendx uint // 环形队列下标,指示元素写入时存放到队列中的位置
recvx uint // 环形队列下标,指示下一个读取的元素的位置
recvq waitq // 等待读取元素的goroutine队列(双向链表)
sendq waitq // 等待写入元素的goroutine队列(双向链表)
lock mutex // 互斥锁,channel不允许并发读写
}
特性
- 无缓冲区通道
- 在没有等待的读操作时,写操作会阻塞当前协程;
- 在没有等待的写操作时,读操作会阻塞当前协程;
- 从已经close的无缓冲通道读数据时,返回通道类型的零值;
- 有缓冲区通道
- 通道已满时,写操作会阻塞当前协程;
- 通道为空时,读操作会阻塞当前协程;
- 通道未满且不为空时,读和写都不会阻塞当前协程;
- 从已经close的有缓冲通道读取数据时,若通道内存在未读取数据则返回该数据,否则返回通道类型的零值;
- channel为nil时,读写操作都会阻塞当前协程;
- 向已经关闭的channel写数据时,会引发panic异常;
重复关闭channel或关闭为nil的channel时,会引发panic异常;
存储结构
向channel写数据
如果等待读取队列recvq不为空,说明channel没有缓冲区或缓冲区中没有数据,此时直接从recvq中取出一个G,数据写入并唤醒这个G后,结束当前操作;
- 如果缓冲区中存在空余位置,将数据写入缓冲区后结束当前操作;
- 如果缓冲区中没有空余位置且recvq为空,创建sudog并将当前G和数据放入sudog,将sudog放入sendq后当前G进入睡眠,等待被读操作唤醒;
向channel读数据
- 如果channel无缓冲区且等待写入队列sendq不为空,直接从sendq中取出sudog,从sudog中读出数据后将sudog中的G唤醒,结束当前操作;
- 缓冲区中有数据且写入队列sendq不为空时,先从buf[recvx]处读取数据,在从sendq中取出sudog,将sudog中的数据写入buf[sendx]后唤醒sudog中的G,结束当前操作;
- 缓冲区中有数据且写入队列sendq为空时,从buf[recvx]处读取数据,结束当前操作;
- 缓冲区中无数据时,创建sudog并将当前G放入sudog中,将sudog放入sendq后当前G进入睡眠,等待被写操作唤醒;
关闭channel
- 关闭channel时,会把等待读取队列recvq中的G全部唤醒,并写入通道类型的零值;
- 关闭channel时,会把等待写入队列sendq中的G全部唤醒,但这些G会引发panic异常;