1. go channel源码-结构体

163 阅读4分钟

1.概述

2.数据结构图

channel结构图:

image.png

3.数据结构描述

3.1 hchan结构体

字段类型含义
qcountuint队列中元素数量
datasizeuint队列最大容量
bufunsafe.Pointer队列起始指针
elemsizeuint16元素的大小
closeduint32当前channel是否已经关闭,1关闭,0未关闭
elemtype*_type元素类型
sendxuint队列中已发送的位置索引
recvxuint队列中已接收的位置索引
recvqwaitq等待channel接收的Goroutine队列(elem <- chan)
sendqwaitq等待channel发送的Goroutine队列(chan <- elem)
lockmutexlock锁保护hchan中的所有字段,以及此通道上被阻塞的sudogs中的多个字段。持有lock的时候,禁止更改另一个G的状态(特别是不要使G状态变成ready),因为这会因为堆栈shrinking而发生死锁。

3.2 waiq结构体

存放正在等待的Goroutine队列

字段类型含义
first*sudog
last*sudog

3.3 sudog结构体

以下字段受此sudog阻塞的通道的hchan.lock保护。shrinkStack依赖于此来处理通道操作中涉及的sudog。

以下字段由sudog阻塞的通道上的hchan.lock保护

字段名数据类型描述
g*g指向等待此等待操作的G(goroutine)结构体的指针
next*sudog在链接列表中指向下一个sudog元素的指针
prev*sudog在链接列表中指向前一个sudog元素的指针
elemunsafe.Pointer数据元素,可能指向堆栈

以下字段在使用semaRoot锁时访问,对于通道,waitlink只由g访问,所有字段(包括上述字段)只有在持有一个semaRoot锁时才能访问:

字段名数据类型描述
acquiretimeint64以纳秒为单位计算的锁定获取时间,从程序启动开始计算
releasetimeint64以纳秒为单位计算的锁释放时间,从程序启动开始计算
ticketuint32该sudog持有的锁定匹配器的票据
isSelectbool指示g是否参与select操作的布尔值
successbool指示通信是否成功,如果g被唤醒是因为在c通道上传递了值,则为true
parent*sudogsemaRoot sled(二叉树)的父元素
waitlink*sudog等待操作的双向链接列表的第一个元素
waittail*sudog等待操作的双向链接列表的最后一个元素
c*hchan指向此sudog正在阻塞的通道对象的指针

3.4 chantype结构体

字段类型含义
typ_type元类型
elem*_type元素
diruintptr

3.5 常数

字段含义
maxAlign8最大内存对齐
hchanSizeunsafe.Sizeof(hchan{}) + uintptr(-int(unsafe.Sizeof(hchan{}))&(maxAlign-1))chanenl结构体大小
debugChanfalse调试模式

4.实现细节

4.1 channel的发送和接收

channel的发送和接收主要考虑三种情况:

  1. 移动缓冲区
  2. 阻塞
  3. 缓冲区复制&&直接发送

1.移动缓冲区

chanenl缓冲区未满的时候,底层环形队列缓冲将会保存消息,此时发送方和接收方都从队列缓存中直接操作消息。

  1. 当channel新增发送方,hchan首先会加锁,其次将发送方的消息体存放进sendx,接着移动sendx指针,最后释放锁。

image.png

  1. 当channel新增接收方,hchan首先会加锁,其次将recvx的消息体取出,接着移动recvx指针,最后释放锁。

image.png

2.阻塞

channel缓冲区 已满且仍有发送方 或者 已空且仍有接收方 的时候,此时新增的发送方G/接收方G将进入阻塞状态。此时,阻塞的G将被包装进sudog结构体中,并以双向链表的形式保存下来。

  1. channel缓冲区已满且仍有发送方,新增的发送方G将进入阻塞状态,以sudog的形式加入sendq

image.png

channel缓冲区已空且仍有接收方,新增的接收方G将进入阻塞状态,以sudog的形式加入recvq中。

image.png

3. 缓存区复制&& 直接发送

在上述第二种情况的基础上,当channel 存在阻塞发送方且新增了接收方 的时候,channel会将缓冲区的数据返回给接收方,同时将阻塞在双向链表队头的发送方唤醒,并将其消息复制进缓冲区。

image.png

当channel 存在阻塞接收方且新增了发送方 的时候,channel会唤醒阻塞的接收方,然后直接把数据从发送方复制到接收方,无需再经过缓冲区,即直接发送。

image.png