Channel底层原理

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中不同情况:

  1. 有正在等待的读取协程

    当有读取的协程正在等待时,直接从等待的读取协程链表中获取第一个协程,并将元素直接复制到对应的协程中,再唤醒被堵塞的协程。

  2. 缓冲区有空余

    如果队列中没有正在等待的协程,但是通道是带缓冲区的,并且当前缓冲区没有满,则向当前缓冲区写入元素。

  3. 缓冲区无空余

    当前通道无缓冲区或者当前缓冲区已经满了,则代表当期那协程的sudog结构需要放入sendq链表末尾,并且当前协程陷入休眠状态,等待被唤醒重新执行。

通道读取

读取与写入原理相似,在运行时调用chanrecv函数

  1. 有正在等待的写入协程

    从等待协程的写入协程链表中获取第一个协程,并将写入的元素直接复制到当前协程中,再唤醒被堵塞的写入协程。

  2. 缓冲区有元素

    读取缓冲区中的数据,并写如当前的读协程中。

  3. 缓冲区无元素

    无缓冲区或者当前缓冲区已经空了,代表当前协程的sudog结构需要放入recvq链表末尾,并将当前协程陷入休眠状态,等待被唤醒重新执行。

原文地址:https://www.cnblogs.com/hzpeng/p/15415299.html