乐趣区

关于golang:Golang底层实现系列channel的底层实现

Golang 底层实现系列 -channel 的底层实现

channel 底层构造

hchan:

type hchan struct {
    qcount   uint           // buffer 中曾经存在的元素个数
    dataqsiz uint           // buffer 的缓存大小(最大元素个数)buf      unsafe.Pointer // buffer 地址
    elemsize uint16            // buffer 中每个元素的大小
    closed   uint32            // channel 是否曾经敞开,0 未敞开
    elemtype *_type // channel 中的元素的类型
    sendx    uint   // buffer 中已发送的索引地位
    recvx    uint   // buffer 中已承受的索引地位
    recvq    waitq  // 期待接管的 sudog(sudog 为封装了 goroutine 和数据的构造)队列
    sendq    waitq  // 期待发送的 sudogo 队列

    lock mutex   // 一个轻量级锁
}

向 channel 中发送

简略流程形容:

  1. 查看 recvq 是否为空,如果不为空,则从 recvq 头部取一个 goroutine,将数据发送过来,并唤醒对应的 goroutine 即可。
  2. 如果 recvq 为空,则将数据放入到 buffer 中。
  3. 如果 buffer 已满,则将要发送的数据和以后 goroutine 打包成 sudog 对象放入到 sendq 中。并将以后 goroutine 置为 waiting 状态。

流程图:

从 channel 中接收数据

简略过程形容:

  1. 查看 sendq 是否为空,如果不为空,且没有缓冲区,则从 sendq 头部取一个 goroutine,将数据读取进去,并唤醒对应的 goroutine,完结读取过程。
  2. 如果 sendq 不为空,且有缓冲区,则阐明缓冲区已满,则从缓冲区中首部读出数据,把 sendq 头部的 goroutine 数据写入缓冲区尾部,并将 goroutine 唤醒,完结读取过程。
  3. 如果 sendq 为空,缓冲区有数据,则间接从缓冲区读取数据,完结读取过程。
  4. 如果 sendq 为空,且缓冲区没数据,则只能将以后的 goroutine 退出到 recvq, 并进入waiting 状态,期待被写 goroutine 唤醒。

流程图(点击图片查看大图):

recv函数外部细节:
(点击查看大图)

ring buffer 实现

channel 的缓冲区通过 ring buffer 实现,同时存在两个标记 sendxrecvx别离来标识写入地位和读取地位。当产生写入是 sendx 会加 1,当达到最大地位时,sendx会回到起始地位。
(点击图片查看大图)

上图展现的是一个缓冲区为 8 的 channel buffer,recvx 指向最早被读取的数据,sendx 指向再次写入时插入的地位。

总结

  1. channel 应用 ring buffer(环形缓冲区)来缓存写入的数据;
  2. hamp 通过两个 list 来保留期待接管和期待发送的 goroutine;
  3. 一个 channel 同一时间只能够有一个 goroutine 在读或者写;

参考文章

Golang channel 源码深度分析

《Go 专家编程》Go channel 实现原理分析

退出移动版