GO 中 Chan 实现原理分享
嗨,我是小魔童哪吒,还记得咱们之前分享过GO 通道 和sync包的使用吗?咱们来回顾一下
- 分享了通道是什么,通道的种类
- 无缓冲,有缓冲,单向通道具体对应什么
- 对于通道的具体实践
- 分享了关于通道的异常情况整理
- 简单分享了sync包的使用
要是对上述内容还有点兴趣的话,欢迎查看文章 GO通道和 sync 包的分享
chan 是什么?
是一种特殊的类型,是连接并发goroutine
的管道
channel 通道是可以让一个 goroutine 协程发送特定值到另一个 goroutine 协程的通信机制。
通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序,这一点和管道是一样的
一个协程从通道的一头放入数据,另一个协程从通道的另一头读出数据
每一个通道都是一个具体类型的导管,声明 channel 的时候需要为其指定元素类型。
本篇文章主要是分享关于通道的实现原理,关于通道的使用,可以查看文章 GO通道和 sync 包的分享 ,这里有详细的说明
GO 中 Chan 的底层数据结构
了解每一个组件或者每一个数据类型的实现原理,咱们都会去看源码中的数据结构是如何设计的
同样,我们一起来看看 GO 的 Chan 的数据结构
GO 的 Chan 的源码实现是在 : src/runtime/chan.go
type hchan struct {
qcount uint // total data in the queue
dataqsiz uint // size of the circular queue
buf unsafe.Pointer // points to an array of dataqsiz elements
elemsize uint16
closed uint32
elemtype *_type // element type
sendx uint // send index
recvx uint // receive index
recvq waitq // list of recv waiters
sendq waitq // list of send waiters
// lock protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex
}
hchan
是实现通道的核心数据结构,对应的成员也是不少,咱们根据源码注释一个参数一个参数的来看看
tag | 说明 |
---|---|
qcount | 当前的队列,剩余元素个数 |
dataqsiz | 环形队列可以存放的元素个数,也就是环形队列的长度 |
buf | 指针,指向环形队列 |
elemsize | 指的的队列中每个元素的大小 |
closed | 具体标识关闭的状态 |
elemtype | 见名知意,元素的类型 |
sendx | 发送队列的下标,向队列中写入数据的时候,存放在队列中的位置 |
recvx | 接受队列的下标,从队列的 这个位置开始读取数据 |
recvq | 协程队列,等待读取消息的协程队列 |
sendq | 协程队列,等待发送消息的协程队列 |
lock | 互斥锁,在 chan 中,不可以并发的读写数据 |
根据上面的参数,我们或多或少就可以知道 GO 中的通道实现原理设计了哪些知识点:
- 指针
- 环形队列
- 协程
- 互斥锁
我们顺便再来看看上述成员的协程队列 waitq
对应的是啥样的数据结构
type waitq struct {
first *sudog
last *sudog
}
sudog
结构是在 src/runtime/runtime2.go
中 ,咱们顺便多学一手
// sudog represents a g in a wait list, such as for sending/receiving
// on a channel.
type sudog struct {
// The following fields are protected by the hchan.lock of the
// channel this sudog is blocking on. shrinkstack depends on
// this for sudogs involved in channel ops.
g *g
next *sudog
prev *sudog
elem unsafe.Pointer // data element (may point to stack)
// The following fields are never accessed concurrently.
// For channels, waitlink is only accessed by g.
// For semaphores, all fields (including the ones above)
// are only accessed when holding a semaRoot lock.
acquiretime int64
releasetime int64
ticket uint32
// isSelect indicates g is participating in a select, so
// g.selectDone must be CAS'd to win the wake-up race.
isSelect bool
// success indicates whether communication over channel c
// succeeded. It is true if the goroutine was awoken because a
// value was delivered over channel c, and false if awoken
// because c was closed.
success bool
parent *sudog // semaRoot binary tree
waitlink *sudog // g.waiting list or semaRoot
waittail *sudog // semaRoot
c *hchan // channel
}
根据源码注释,咱们大致知道sudog
是干啥的
Sudog
表示等待列表中的 g,例如在一个通道上发送/接收
Sudog
是很必要的,因为g↔synchronization对象关系是多对多
一个 g 可能在很多等候队列上,所以一个 g 可能有很多sudogs
而且许多 g 可能在等待同一个同步对象,所以一个对象可能有许多sudogs