golang的channel机制底层实现

0.主要结构

Go 复制代码
type hchan struct {
	// chan 里元素数量
	qcount   uint
	// chan 底层循环数组的长度
	dataqsiz uint
	// 指向底层循环数组的指针
	// 只针对有缓冲的 channel
	buf      unsafe.Pointer
	// chan 中元素大小
	elemsize uint16
	// chan 是否被关闭的标志
	closed   uint32
	// chan 中元素类型
	elemtype *_type // element type
	// 已发送元素在循环数组中的索引
	sendx    uint   // send index
	// 已接收元素在循环数组中的索引
	recvx    uint   // receive index
	// 等待接收的 goroutine 队列
	recvq    waitq  // list of recv waiters
	// 等待发送的 goroutine 队列
	sendq    waitq  // list of send waiters

	// 保护 hchan 中所有字段
	lock mutex
}

channel主要调用发送和接收两个方法,

1.接收val:=<-ch,内部主要调用

Go 复制代码
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool){
...
}

这里c就是定义的channel,ep是接收数据的地址也就是&val,block默认为true,selected标志位在select场景使用,received标志位在判断通道关闭时使用。

主要处理以下情形:

1.1 c为空,

如果不阻塞,直接返回(false,false);

如果阻塞,接收一个nil的channel时候直接将此go挂起;

1.2 通道还没有准备好接收数据(对于非缓存而言就是还没有发送方,也就是c的sendq里没有等待发送的go,对于缓存而言就是buf还是空的)

如果不阻塞,直接返回(false,false);

如果阻塞,则继续往下走;

1.3 若通道已经关闭,从一个已经关闭的通道执行接收,将返回零值,并返回(true,false);

1.4 若c.sendq队列有go存在

对于非缓存而言,直接将数据从发送的go要发送的数据地址拷贝至接收地址ep也就是&val;

对于缓存而言说明buf满了,应先处理buf里数据然后将发送的数据放到buf也就是:

找到recvx处buf[recvx]并拷贝至接收地址ep;然后从发送go的要发送的数据地址拷贝至buf[recvx],接收下标recvx++;

1.5 若c.buf有数据但没有满,应该先处理buf的:

找到recvx处buf[recvx]并拷贝至接收地址ep;然后清理掉该位置数据,接收下标更新recv++;buf的数组元素个数更新c.qcount--

1.6 若最后,通道还是没有准备好接收数据(对于非缓存而言就是还没有发送方,也就是c的sendq里没有等待发送的go,对于缓存而言就是buf还是空的),则构建一个sudog,其elem存储接收数据的地址ep,g存储当前go,c存储此通道。将这个sudog放入c.recvq队列然后挂起。

2.发送ch<-3,内部主要调用

Go 复制代码
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool{
...
}

这里c就是定义的channel,ep是发送数据的地址,block默认为true。

主要处理以下情形:

2.1 c为空,

不能阻塞,直接返回 false,表示未发送成功

如果阻塞,发送一个nil的channel时候直接将此go挂起;

2.2 通道还没有准备好发送数据(对于非缓存而言就是还没有接收方,也就是c的recvq里没有等待接收的go,对于缓存而言就是buf还是满的)

如果不阻塞,直接返回false;

如果阻塞,则继续往下走;

2.3 若通道已经关闭,从一个已经关闭的通道执行发送,将panic;

2.4 若c.recvq队列有go存在

无论是缓存还是非缓存都说明buf(尽管非缓存没有这个概念)为空不用操作buf,因此可以直接将发送数据发给接收者,这里也就是取出c.recvq队列头部一个接收go,将要发送的数据拷贝至接收go的接收地址并返回true;

2.5 若c.buf有空闲空间但没有空,应该发送数据放入buf:

找到sendx处buf[sendx]并将要发送数据拷贝至此;发送下标更新send++;buf的数组元素个数更新c.qcount++

2.6 若最后,通道还是没有准备好发送数据(对于非缓存而言就是还没有接收方,也就是c的recvq里没有等到接收的go,对于缓存而言就是buf还是满的),则构建一个sudog,其elem存储要发宋数据的地址ep,g存储当前go,c存储此通道。将这个sudog放入c.sendq队列然后挂起。

3 总结一下操作 channel 的结果:

|--------|-------------|----------------|-------------------------------------------------------|
| 操作 | nil channel | closed channel | not nil, not closed channel |
| close | panic | panic | 正常关闭 |
| 读<-ch | 阻塞 | 零值 | 阻塞或正常读取数据。缓冲型 channel 为空或非缓冲型 channel 没有等待发送者时会阻塞 |
| 写ch<- | 阻塞 | panic | 阻塞或正常写入数据。非缓冲型 channel 没有等待接收者或缓冲型 channel buf 满时会被阻塞 |

相关推荐
程序员三明治2 小时前
【重学计网】TCP如何保证可靠传输?怎么保证可靠性?可靠传输的原理?
java·网络·后端·网络协议·tcp/ip·tcp·可靠传输
FL16238631295 小时前
[C#][winform]基于yolov8的水表读数检测与识别系统C#源码+onnx模型+评估指标曲线+精美GUI界面
开发语言·yolo·c#
cnxy1888 小时前
围棋对弈Python程序开发完整指南:步骤1 - 棋盘基础框架搭建
开发语言·python
上进小菜猪8 小时前
基于 YOLOv8 的驾驶员疲劳状态识别系统实战(含完整源码与可视化界面)
后端
上进小菜猪8 小时前
基于 YOLOv8 的交通标识与设施识别系统(含完整源码)
后端
程序员-周李斌9 小时前
Java 死锁
java·开发语言·后端
布列瑟农的星空9 小时前
还在手动翻译国际化词条?AST解析+AI翻译实现一键替换
前端·后端·ai编程
武子康10 小时前
大数据-197 K折交叉验证实战:sklearn 看均值/方差,选更稳的 KNN 超参
大数据·后端·机器学习
JasmineWr10 小时前
CompletableFuture相关问题
java·开发语言
零雲10 小时前
java面试:知道java的反射机制吗
java·开发语言·面试