go channel
本文最后更新于 2024-07-20,文章内容可能已经过时。
概念
Go中的channel是一个队列,遵循先进先出的原则,负责协程之间的通信(Go建议不要使用共享内存来通信,而要通过通道来实现内存共享,CSP(Communicating Sequential Process)并发模型,就是根据goroutine和channel实现的)
使用场景
停止信号监听
定时任务
生产和消费解偶
控制并发数量
底层数据结构
通过var声明或者make创建的channel变量是存储在函数栈帧上的指针,占用8个字节,指向堆上的chan结构体。
源码包 src/runtime/chan.go
定义了hchan的数据结构:
type hchan struct {
qcount uint // total data in the queue 环形队列里面的总的数据量
dataqsiz uint // size of the circular queue // 环形队列大小 就是 make chan时 申请的大小
buf unsafe.Pointer // points to an array of dataqsiz elements // 指向环形队列的指针
elemsize uint16 // 储存的元素类型占空间大小
closed uint32 // chan 状态 1 关闭 0 未关闭
elemtype *_type // element type // 元素类型 // make chan是 指定的类型 不过这个类型要进行运行时转换 但对应关系是这样的
sendx uint // send index // 发送索引
recvx uint // receive index // 获取索引
recvq waitq // list of recv waiters // 获取协程等待队列
sendq waitq // list of send waiters // 发送携程等待队列
lock mutex // 锁
}
等待队列:
双向链表,每个节点是一个sudog结构体变量,记录哪个协程在等待,sudo g,等待的是哪个channel,等待的是那些数据。
type waitq struct {
first *sudog //指向等待队列中第一个等待的 sudog 结构体的指针
last *sudog //指向等待队列中最后一个等待的 sudog 结构体的指针
}
type sudog struct {
g *g // 关联的 goroutine
next *sudog // 下一个 sudog 结构体
prev *sudog // 上一个 sudog 结构体
elem unsafe.Pointer // 元素指针
acquiretime int64 // 获取时间
releasetime int64 // 释放时间
ticket uint32 // 票据(用于调度)
isSelect bool // 是否处于 select 操作
success bool // 操作是否成功
parent *sudog // 父 sudog(用于嵌套等待)
waitlink *sudog // 等待链接(用于等待队列)
waittail *sudog // 等待尾部(用于等待队列)
c *hchan // 关联的通道
}
操作
创建
ch:=make(chan int)
ch:=make(chan int,3)
创建时会做一些检查:
元素大小不超过64k
元素对齐大小不超过maxAlign,8字节
计算出来的内存是否超过限制
创建时的策略:
无缓冲会直接给chan分配内存
有缓冲,并且元素不包含指针,会给hchan和底层数组分配一段连续的地址
有缓冲,并且元素包含指针,会给hchan和底层数组分别分配地址
发送
runtime.chansend
默认为阻塞式发送
非阻塞式发送通过select发送
Block参数true 或者false控制
1,发送会类型检查,数据竞争,同时读写
有读等待队列不为空,会发送给第一个等待的chan
为空,直接写入到循环数组,buf没满发送到队尾。满了就回阻塞,就发送给写等待队列。
接收
Runtime chanrecev
直接读取,忽略返回值,接收返回值,接受ok判断,forr遍历读取。
非阻塞式也是通过select
1检查
2接受
如果有写等待队列有,就会
无缓冲直接读
有缓冲,把循环队列的队首拷贝到
关闭
close(ch)
todo....