channel的实现
上篇文章我们比较笼统的介绍了一下channel是什么?这篇文章我们从源码来分析channel是如何实现的!
文章中涉及的chan源码都在chan.go文件中
chan的定义
type hchan struct {
qcount uint // 队列中的数据个数
dataqsiz uint // 环形队列的大小
buf unsafe.Pointer // 环状(datasiz)队列指向的数组地址
elemsize uint16 // elemtype占的内存大小
closed uint32 // chan是否被关闭
elemtype *_type // chan可接收的数据类型,在chan [type]时被定义的类型
sendx uint // 记录当前需要被sendchan函数发布的数据索引
recvx uint // 记录当前需要被recechan函数消费的数据索引
recvq waitq // 接收队列
sendq waitq // 发送队列
lock mutex // 对象锁,用来防止hchan实例被多个线程同时修改
}
上篇文章中我们提到的runtime.sendchan1和runtime.chanrecv1函数,Go会将它们分别封装的waitq对象,在hchan结构体中分别对应sendq和recvq字段。
在hchan结构体中以下几个重要字段我们先说明一下:
-
elemtype 这个字段用来记录当前chan支持的数据类型,它是在我们创建chan变量的时候被指定的。
# go中创建chan的几种方式: # 1. 声明 var c1 chan int # 2. 声明并初始化 c2:=make(chan int) # 声明与初始化的区别在于是否申请内存 c1: nil c2: 指向某个内存地址 -
recvq
接收队列,当我使用c1:=<-chan这种方式来接收数据时,Go会将这个语法编译为对应的waitq类型,并保存到当前chan实例对应recvq字段中 -
sendq
发送队列,当我使用chan<-c1这种方式来接收数据时,Go会将这个语法编译为对应的waitq类型,并保存到当前chan实例对应sendq字段中 -
lock
lock字段用来锁住当前chan实例,因为我们的发送队列与接收队列可能在不同的协程上运行,所以为了避免数据不一致的问题,这里就采用锁的机制来搞定。
其它字段,当我们后面分析具体实现的时候再逐一解释
waitq的定义
waitq类型用于实现send队列与recv队列,这个类型中定义了队列的开头与末尾元素的位置,这也就意味着要访问队列中的元素必须从两端开始遍历,那么Go中的channel是有序的吗?
type waitq struct {
first *sudog
last *sudog
}