Golang基础——Chan实现

/* 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逻辑

上一篇
下一篇