先看下源码,源码位于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
}
type waitq struct {
first *sudog
last *sudog
}
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
// isSelect indicates g is participating in a select, so
// g.selectDone must be CAS'd to win the wake-up race.
isSelect bool
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
parent *sudog // semaRoot binary tree
waitlink *sudog // g.waiting list or semaRoot
waittail *sudog // semaRoot
c *hchan // channel
}
qcount uint // 当前队列中剩余元素个数
dataqsiz uint // 环形队列长度,即缓冲区的大小,即make(chan T,N),N.
buf unsafe.Pointer // 环形队列指针
elemsize uint16 // 每个元素的大小
closed uint32 // 表示当前通道是否处于关闭状态。创建通道后,该字段设置为0,即通道打开; 通过调用close将其设置为1,通道关闭。
elemtype *_type // 元素类型,用于数据传递过程中的赋值;
sendx uint和recvx uint是环形缓冲区的状态字段,它指示缓冲区的当前索引 - 支持数组,它可以从中发送数据和接收数据。
recvq waitq // 等待读消息的goroutine队列
sendq waitq // 等待写消息的goroutine队列
lock mutex // 互斥锁,为每个读写操作锁定通道,因为发送和接收必须是互斥操作。
这里sudog代表goroutine。
如创建带缓冲的通道:ch := make(chan int, 3),,底层的数据模型如下图:
image.png
向channel中写入数据
ch <- 3。底层hchan数据流程如图
image.png
image.png
发送操作流程:
1.锁定整个通道结构。
2.确定写入。如果recvq不为空,尝试recvq从等待队列中等待goroutine,然后将元素直接写入goroutine。
3.如果recvq为空,则确定缓冲区是否可用,如果可用,则从当前goroutine复制数据到缓冲区。
4.如果缓冲区已满,则要写入的元素将保存在当前正在执行的goroutine的结构中,并且当前goroutine将在sendq中排队并从运行时挂起。
5.写入完成释放锁。
image.png
从channel中读取数据
ch <- 3,底层hchan数据流程图:
image.png
image.png
读取操作概要:
1.先获取channel全局锁。
2.尝试从sendq等待队列中获取等待的goroutine。
3.如果有等待的goroutine,且没有缓冲区,取出goroutine并读取数据,然后唤醒这个goroutine,结束读取释放锁。
4.如果有等待的goroutine,且有缓冲区(缓冲区已满),从缓冲区队首取出数据,再从sendq中取出一个goroutine,将goroutine中的数据取出存入buf队尾,结束读取释放锁。
5.如果没有等待的goroutine,且缓冲区有数据,直接读取缓冲区数据,结束读取释放锁。
6.如果没有等待的goroutine,且没有缓冲区或缓冲区为空,则当前goroutine加入到recvq排队,进入睡眠,等待被写的goroutine唤醒,结束读取释放锁。
流程图:
image.png