24

Golang channel 之 数据结构

 4 years ago
source link: https://studygolang.com/articles/25210
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

channel的作用

channel被设计用来实现goroutine间的通信,按照golang的设计思想:以通信的方式共享内存。

channel的内存布局

例如如下代码中的make函数会在堆上分配一个runtime.hchan类型的数据结构,ch是存在于函数f栈帧上的一个指针,指向堆上的hchan数据结构。

func f() {
    ch := make(chan int)
    ...
}

至于为什么是堆上的一个结构体:首先,要实现channel这样的复杂功能,肯定不是几个字节可以搞定的,所以需要一个struct来实现;其次,这种被设计用来实现协程间通信的组件,其作用域和生命周期不可能仅限于某个函数内部,所以golang直接将其分配在堆上。

channel的数据结构

下面结合在channel中的作用,解读一下hchan中都有哪些字段:

1)协程间通信肯定涉及到并发访问,所以要有锁来保护整个数据结构:

type hchan struct {
    ...
    lock mutex
}

2)channel分为“无缓冲”和“有缓冲”两种,对于有缓冲channel来讲,需要有相应的内存来存储数据,实际上就是一个数组,需要知道数组的地址、容量、元素的大小,以及数组的长度也就是已有元素个数,加上这几个字段后,上面的结构体就变成了这样:

type hchan struct {
    qcount   uint           // 数组长度,即已有元素个数
    dataqsiz uint           // 数组容量,即可容纳元素个数
    buf      unsafe.Pointer // 数组地址
    elemsize uint16         // 元素大小
    ...
}

3)因为golang运行时中内存复制、垃圾回收等机制依赖数据的类型信息,所以hchan中还要有一个指针,指向元素类型的类型元数据:

type hchan struct {
    ...
    elemtype *_type // 元素类型
    ...
}

4)channel支持交替的读写(称send为写,recv为读,更简洁),有缓冲channel内的缓冲数组会被作为一个“环型”来使用,当下标超过数组容量后会回到第一个位置,所以需要有两个字段记录当前读和写的下标位置:

type hchan struct {
    ...
    sendx    uint   // 下一次写下标位置
    recvx    uint   // 下一次读下标位置
    ...
}

5)当读和写请求不能立即被满足时,需要能够让当前协程在channel上等待,当请求能够被满足时,要能够立即唤醒等待的协程,所以要有两个等待队列,分别针对读和写:

type hchan struct {
    ...
    recvq    waitq  // 读等待队列
    sendq    waitq  // 写等待队列
    ...
}

6)channel是能够被close的,所以要有一个字段记录是否已经close掉了:

type hchan struct {
    ...
    closed   uint32
    ...
}

最后整合起来,runtime.hchan结构是这个样子:

type hchan struct {
    qcount   uint           // 数组长度,即已有元素个数
    dataqsiz uint           // 数组容量,即可容纳元素个数
    buf      unsafe.Pointer // 数组地址
    elemsize uint16         // 元素大小
    closed   uint32
    elemtype *_type // 元素类型
    sendx    uint   // 下一次写下标位置
    recvx    uint   // 下一次读下标位置
    recvq    waitq  // 读等待队列
    sendq    waitq  // 写等待队列
    lock     mutex
}

本篇先到这里,至于channel的读写操作和select机制,留到后面的文章中解读。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK