一、channel
channel分为有buffer的和没有buffer的。
没有buffer的可以当成有buffer但是buffersize为0的情况。
buffer数据结构:
type hchan struct {
qcount uint // 当前chan中有多少数据
dataqsiz uint // 环形数组队列的大小,也就是我们定义的缓冲区大小
buf unsafe.Pointer // 指向环形数组队列的指针
elemsize uint16
closed uint32
elemtype *_type // element type
sendx uint // 发送时插入的位置(环形数组的下标)
recvx uint // 接收时取数据的位置(环形数组的下标)
recvq waitq // 接收链表,当buf为空的时候,打包goroutine现场后放在这里
sendq waitq // 发送链表,当buf满的时候,打包goroutine现场后放在这里
// 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
}
发送流程
像图中发送数据到channel中,每次qcount和sendx会随之变化,sendx会在插入前标志当前的插入位置变到插入后标志下一个数据插入位置(由于是环形数组,所以如果在最后位置插入后索引归0)
当buf里面的数据满的时候,再往里面发送数据,此时qcount==dataqsize表示满,此时我们会将当前G的现场与channel打包成一个sudog的结构,链在sendq上。
上图为正常的发送流程,用以演示各个字段在流程中的变化。事实上发送时还需要判断recvq链表是否有sudog:
我们知道,sendq中存放的是等待发送的sudog,那么同样的recvq存放的就是等待接收的sudog。可以想象到,当recvq中有sudog节点的时候就说明我们的缓冲区已经没有数据可以取了,才会将接收的g放到recvq中。此时,我们需要发送的内容应该立即被拿走,不该再放到buf或sendq中。
完整的发送流程如下:
接收流程
同样的,当做从channel中接收数据的动作时,会先判断buf是否为空,为空的话进行现场打包成sudog链在recvq的链表上。
完整的接收流程如下:
与发送流程有所不同的是,当buf数据满并且sendq中有sudog的时候,我们还需要判断是否有缓冲区。
- 如果有缓冲区的话我们需要维护buf:
- 先将当前的recvx索引的数据取出
- 然后将sudog中的elem数据取出
- 再将sudog取出的数据copy到buf空出来的位置。(sendq和recvq是链表结构但是也符合先进先出,在waiq结构中会保存first sudog和last sudog的指针位置,方便进行链表的入队与出队操作)
- 如果没有缓冲区,那我们直接就可以将sudog的数据取出接收。
为什么发送的时候不需要判断是否有缓冲区而接收的时候需要判断呢?
我们可以从接收流程中发现,我们会在buf为空的时候才会往recvq追加sudog,那么也就是说在接收流程中,recvq只要不为空那就说明buf是空的,那么没有缓冲区和有缓冲区也都是等价于空的buf,所以无需判断。
但是在接收流程中,如果sendq不为空的话。
- 如果有缓冲区,说明buf一定是满的,因为需要维护好先后顺序,所以我们要维护buf和sendq链表。
- 没有缓冲区,无需维护buf,所以直接从sendq中找数据内容。
ps:
- gopark()是挂起的意思,会对应一个goready()唤醒。
- 挂起与唤醒:
- ①sender挂起的一定是由receiver或close唤醒
- ②receiver挂起的一定是由sender或close唤醒。