go chan管道读写解读

2024-04-26  本文已影响0人  Feel_狗焕

1、channel数据结构:

channel 是一种类型安全的(并发安全)、阻塞的、先进先出(FIFO)的数据结构,确保发送的数据按照发送的顺序接收。

通过 var 声明或者 make 函数创建的 channel 变量是一个存储在函数栈上的指针,指向堆上的 hchan 结构体。底层数据结构如下:


type hchan struct {

qcount uint // 队列中剩余的元素个数

dataqsiz uint // 环形队列长度,即可以存放的元素个数, make初始化时指定

// channel 分为无缓冲和有缓冲两种。

// 对于有缓冲的 channel 存储数据,使用了 ring buffer(环形缓冲区)来缓存写入的数据,本质是循环数组。

// 为什么是循环数组?普通数组不行吗?普通数组容量固定,更适合指定的空间,弹出元素时,普通数组需要全部都前移。

// 指向底层循环数组的指针(环形缓冲区)缓存区,实际上就是环形队列(有环形队列就有缓冲区,否则没有缓冲区),指向环形队列首部的指针,基于环形队列实现,大小等于make初始化channel时指定的环形队列长度,如果make初始化channel时不指定dataqsiz,则buf=0。只有缓冲型的channel才有buf

buf unsafe.Pointer

elemsize uint16 // 元素的大小

closed uint32 // channel是否关闭的标志

elemtype *_type // channel中的元素类型

// 当下标超过数组容量后会回到第一个位置,所以需要有两个字段记录当前读和写的下标位置

sendx uint // 写入数据的索引,即从哪个位置开始写入数据,取值[0, dataqsiz)

recvx uint // 读取数据的索引,即从哪个位置开始读取数据,取值[0, dataqsiz)

//接收等待队列,链表结构,长度无限长, 读取数据的goroutine等待队列, 如果channel的缓冲区为空或者没有缓冲区,读取数据的goroutine被阻塞,加入到recvq等待队列中。因读阻塞的goroutine会被向channel写入数据的goroutine唤醒

recvq waitq

//发送等待队列,链表结构,长度无限长, 写入数据的goroutine等待队列, 如果channel的缓冲区为满或者没有缓冲区,写入数据的goroutine被阻塞,加入到sendq等待队列中。因写阻塞的goroutine会被从channel读取数据的goroutine唤醒

sendq waitq

//并发控制锁, 同一时刻,只允许一个, channel不允许并发读写

lock mutex

}

1.1、环形队列:

chan内部实现了一个环形队列作为其缓冲区,队列的长度是创建chan时指定的。 下图展示了一个可缓存6个元素的channel示意图:

image.jpeg

1.2、goroutine等待队列:

从channel读数据,如果channel缓冲区为空或者没有缓冲区,当前goroutine会被阻塞。向channel写数据,如

果channel缓冲区已满或者没有缓冲区,当前goroutine会被阻塞。 被阻塞的goroutine将会挂在channel的等待队列中:

下图展示了一个没有缓冲区的channel,有几个goroutine阻塞等待读数据:

image.jpeg

注意,一般情况下recvq和sendq至少有一个为空。只有一个例外,那就是同一个goroutine使用select语句向 channel一边写数据,一边读数据。

1.3、类型信息:

一个channel只能传递一种类型的值,类型信息存储在hchan数据结构中。

2、channel读写流程:

G表示一个goroutine

2.1、写入:

image.jpeg

过程描述:

2.2、读取:

image.jpeg

虚线表示recvq中堵塞的G被唤醒的流程,如果G没有被唤醒,则一直堵塞下去,此时关闭channel,会得到channel类型的零值

过程描述:

上一篇 下一篇

猜你喜欢

热点阅读