channel 原理
channel 原理
原理
channel 是 Go 中实现 goroutine 之间通信和同步 的核心机制。
底层数据结构是 hchan,主要由环形队列 + 锁 + 发送/接收队列 + 调度器组成。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
type hchan struct {
qcount uint // 队列中剩余元素数量
dataqsiz uint // 循环队列的长度(channel的大小)
buf unsafe.Pointer // 长度为dataqsiz的底层数组指针,缓存型channel特有
elemsize uint16 // 元素大小
closed uint32 // 是否关闭
elemtype *_type // 接收、发送的的元素类型
sendx uint // 已发送元素在循环队列中的索引位置
recvx uint // 已接收元素在循环队列中的索引位置
recvq waitq // 接收者sudog等待队列(阻塞等待接收的goroutine)
sendq waitq // 发送者sudog等待对列(阻塞等待接收的goroutine)
lock mutex // 互斥锁
}
- 环形队列: 当 chan 是有缓冲型,负责缓存数据。
- 锁:并发控制
- 发送/接收队列:保存发送/接收阻塞等待的 goroutine 。
- 调度器:由 Go runtime 调度器完成
发送数据时,如果有等待的接收 goroutine,会直接将数据交给接收者;如果没有且缓冲区未满,则写入环形队列;如果缓冲区已满,则发送 goroutine 会进入 sendq 队列并被阻塞。
接收数据时,如果缓冲区有数据则直接读取;如果没有但有等待发送者,则直接接收数据;否则接收 goroutine 会进入 recvq 队列阻塞。
这里思考一个问题,那 goroutine1 和 goroutine2 又怎么互相知道自己的数据 ”到“ 了呢?
channel结构中的recvq、sendq保存着阻塞等待的goroutine,但goroutine1向环型队列中发送数据时,就会从recvp取出goroutine并唤醒。
channel OR mutex
- 关注数据流动,考虑使用channel解决
- 数据不流动,保护数据,使用mutex
关闭channel原则
The Channel Closing Principle:不要在接收端关闭channel,也不要关闭有多个并发发送者的channel
打破The Channel Closing Principle解决方案
panic与recover
1 2 3 4 5 6 7 8 9 10 11 12
func SafeSend(ch chan T, value T) (closed bool) { defer func() { if recover() != nil { // the return result can be altered // in a defer function call closed = true } }() ch <- value // panic if ch is closed return false // <=> closed = false; return }
- sync.Once关闭channel
- sync.Mutex关闭channel
参考
- https://mp.weixin.qq.com/s/ZXYpfLNGyej0df2zXqfnHQ
- https://www.cnblogs.com/-wenli/p/12710361.html
- https://segmentfault.com/a/1190000019172554
This post is licensed under CC BY 4.0 by the author.