1.概述
2.数据结构图
channel结构图:
3.数据结构描述
3.1 hchan结构体
| 字段 | 类型 | 含义 |
|---|---|---|
| qcount | uint | 队列中元素数量 |
| datasize | uint | 队列最大容量 |
| buf | unsafe.Pointer | 队列起始指针 |
| elemsize | uint16 | 元素的大小 |
| closed | uint32 | 当前channel是否已经关闭,1关闭,0未关闭 |
| elemtype | *_type | 元素类型 |
| sendx | uint | 队列中已发送的位置索引 |
| recvx | uint | 队列中已接收的位置索引 |
| recvq | waitq | 等待channel接收的Goroutine队列(elem <- chan) |
| sendq | waitq | 等待channel发送的Goroutine队列(chan <- elem) |
| lock | mutex | lock锁保护hchan中的所有字段,以及此通道上被阻塞的sudogs中的多个字段。持有lock的时候,禁止更改另一个G的状态(特别是不要使G状态变成ready),因为这会因为堆栈shrinking而发生死锁。 |
3.2 waiq结构体
存放正在等待的Goroutine队列
| 字段 | 类型 | 含义 |
|---|---|---|
| first | *sudog | |
| last | *sudog |
3.3 sudog结构体
以下字段受此sudog阻塞的通道的hchan.lock保护。shrinkStack依赖于此来处理通道操作中涉及的sudog。
以下字段由sudog阻塞的通道上的hchan.lock保护
| 字段名 | 数据类型 | 描述 |
|---|---|---|
| g | *g | 指向等待此等待操作的G(goroutine)结构体的指针 |
| next | *sudog | 在链接列表中指向下一个sudog元素的指针 |
| prev | *sudog | 在链接列表中指向前一个sudog元素的指针 |
| elem | unsafe.Pointer | 数据元素,可能指向堆栈 |
以下字段在使用semaRoot锁时访问,对于通道,waitlink只由g访问,所有字段(包括上述字段)只有在持有一个semaRoot锁时才能访问:
| 字段名 | 数据类型 | 描述 |
|---|---|---|
| acquiretime | int64 | 以纳秒为单位计算的锁定获取时间,从程序启动开始计算 |
| releasetime | int64 | 以纳秒为单位计算的锁释放时间,从程序启动开始计算 |
| ticket | uint32 | 该sudog持有的锁定匹配器的票据 |
| isSelect | bool | 指示g是否参与select操作的布尔值 |
| success | bool | 指示通信是否成功,如果g被唤醒是因为在c通道上传递了值,则为true |
| parent | *sudog | semaRoot sled(二叉树)的父元素 |
| waitlink | *sudog | 等待操作的双向链接列表的第一个元素 |
| waittail | *sudog | 等待操作的双向链接列表的最后一个元素 |
| c | *hchan | 指向此sudog正在阻塞的通道对象的指针 |
3.4 chantype结构体
| 字段 | 类型 | 含义 |
|---|---|---|
| typ | _type | 元类型 |
| elem | *_type | 元素 |
| dir | uintptr |
3.5 常数
| 字段 | 值 | 含义 |
|---|---|---|
| maxAlign | 8 | 最大内存对齐 |
| hchanSize | unsafe.Sizeof(hchan{}) + uintptr(-int(unsafe.Sizeof(hchan{}))&(maxAlign-1)) | chanenl结构体大小 |
| debugChan | false | 调试模式 |
4.实现细节
4.1 channel的发送和接收
channel的发送和接收主要考虑三种情况:
- 移动缓冲区
- 阻塞
- 缓冲区复制&&直接发送
1.移动缓冲区
当chanenl缓冲区未满的时候,底层环形队列缓冲将会保存消息,此时发送方和接收方都从队列缓存中直接操作消息。
- 当channel新增发送方,
hchan首先会加锁,其次将发送方的消息体存放进sendx,接着移动sendx指针,最后释放锁。
- 当channel新增接收方,
hchan首先会加锁,其次将recvx的消息体取出,接着移动recvx指针,最后释放锁。
2.阻塞
当channel缓冲区 已满且仍有发送方 或者 已空且仍有接收方 的时候,此时新增的发送方G/接收方G将进入阻塞状态。此时,阻塞的G将被包装进sudog结构体中,并以双向链表的形式保存下来。
- 当channel缓冲区已满且仍有发送方,新增的发送方G将进入阻塞状态,以sudog的形式加入
sendq中
当channel缓冲区已空且仍有接收方,新增的接收方G将进入阻塞状态,以sudog的形式加入recvq中。
3. 缓存区复制&& 直接发送
在上述第二种情况的基础上,当channel 存在阻塞发送方且新增了接收方 的时候,channel会将缓冲区的数据返回给接收方,同时将阻塞在双向链表队头的发送方唤醒,并将其消息复制进缓冲区。
当channel 存在阻塞接收方且新增了发送方 的时候,channel会唤醒阻塞的接收方,然后直接把数据从发送方复制到接收方,无需再经过缓冲区,即直接发送。