/* Channel数据结构
* 缓冲区本质为环形队列,
* 通过sendx、recvx索引缓冲区和recvq、sendq Goroutine队列完成读写操作
*/
type hchan struct {
qcount uint // 队列中的总元素个数
dataqsiz uint // 环形队列大小,即可存放元素的个数
buf unsafe.Pointer // 环形队列指针
elemsize uint16 //每个元素的大小
closed uint32 //标识关闭状态
elemtype *_type // 元素类型
sendx uint // 发送索引,元素写入时存放到队列中的位置
recvx uint // 接收索引,元素从队列的该位置读出
recvq waitq // 等待读消息的goroutine队列
sendq waitq // 等待写消息的goroutine队列
lock mutex //互斥锁,chan不允许并发读写
}
Goroutine对Chan操作应为原子操作(由互斥锁保证),故并发安全;并使得一般情况下 recvq 和 sendq 至少有一个为空。只有一个例外,那就是同一个 goroutine 使用 select 语句向 channel 一边写数据,一边读数据。
总的来说根据读写操作本身、有无缓冲区 *决定* Goroutine(自身或队列中的reader、writer)状态、所操作数据的处理方式。
Select操作
本质上在编译后会对case中的通信操作套取Chan.go中的对应代理函数,要点在于明确传入非阻塞参数的指示,以避免Select操作Goroutine在该case通信操作进入阻塞。
Close操作
关闭 channel 时会将 recvq 中的 G 全部唤醒,,本该写入 G 的数据位置为 nil。将 sendq 中的 G 全部唤醒,但是这些 G 会 panic。
为何sendq中的G需要panic?
sendq中的G未完成发送通信操作,此时关闭通道后被唤醒的writer,符合Send Closed Chan Panic逻辑