hchan结构体
通道在运行时是一个特殊的hchan结构体,结构体内容如下:
- qcount 通道队列中数据个数
- dataqsiz 通道队列中的数据大小
- buf 存放实际数据的指针
- elemsize 通道类型大小
- closed 通道是否关闭
- elemtype 通道类型
- sendx 记录发送者在buf中的序号
- recvx 记录接受者在buf中的序号
- recvq 读取的阻塞协程队列
- sendq 写入的阻塞协程队列
- lock 锁,并发保护
线性数组模拟环形队列
有缓存通道存储在buf中的数据虽然是线性的数组,但是用数组和序号revcx和recvq模拟了一个环形队列,recvx可以找到buf哪个位置获取通道中的元素,而sendx能够找到写入时放入buf的位置,这样做主要是为了重用已经使用过的空间。recvx到sendx的距离代表通道队列中元素的数量。
当到达循环队列末尾时,sendx会置为0,以防止下一次写入0号位置,开始循环利用空间。这样同样意味着通道中只能放入指定大小的数据,当通道中的数据满了以后,再次写入数据将陷入等待,直到第0号位置被取出后,才能继续写入。
通道初始化
通道初始化在运行时调用了makechan函数,第1个参数代表通道的类型,第2个参数通道中元素的大小。makechan会判断元素的大小,对齐等。最重要的是它会在内存中分配元素大小。
当分配大小为0是,只要在内存中分配hchan结构体的大小即可
当通道的元素中不包含指针时,连续分配hchan结构体大小+size元素大小
当通道元素中包含指针时,需要单独分配内存空间,因为当通道元素中包含指针时,需要单独分配空间才能正常进行垃圾
通道写入
运行时调度用chansend方法,分为3中不同情况:
-
有正在等待的读取协程
当有读取的协程正在等待时,直接从等待的读取协程链表中获取第一个协程,并将元素直接复制到对应的协程中,再唤醒被堵塞的协程。
-
缓冲区有空余
如果队列中没有正在等待的协程,但是通道是带缓冲区的,并且当前缓冲区没有满,则向当前缓冲区写入元素。
-
缓冲区无空余
当前通道无缓冲区或者当前缓冲区已经满了,则代表当期那协程的sudog结构需要放入sendq链表末尾,并且当前协程陷入休眠状态,等待被唤醒重新执行。
通道读取
读取与写入原理相似,在运行时调用chanrecv函数
-
有正在等待的写入协程
从等待协程的写入协程链表中获取第一个协程,并将写入的元素直接复制到当前协程中,再唤醒被堵塞的写入协程。
-
缓冲区有元素
读取缓冲区中的数据,并写如当前的读协程中。
-
缓冲区无元素
无缓冲区或者当前缓冲区已经空了,代表当前协程的sudog结构需要放入recvq链表末尾,并将当前协程陷入休眠状态,等待被唤醒重新执行。