Golang空通道考究
一、简介
最近看到了一些帖子和博客在讨论golang里面对空通道 [1] 进行读写操作。本人也比较好奇,就实验了一下,发现和大家的描述都有出入,只好再看源码分析。
二、结论要点
对 空通道 读写操作会有以下结果:
- 形如
<- chan
、chan<-
,这种形式的读写会直接阻塞所在协程,并且无法解除阻塞 - 形如
select {case _ <- chan}
,这种形式的读操作会直接导致程序崩溃[2]
三、操作与分析
1、简单源码实验
验证读写休眠:
func main() {
var c chan struct{}
go func() {
fmt.Println("开始接收")
<-c
fmt.Println("接收完成")
}()
go func() {
fmt.Println("开始发送")
c <- struct{}{}
fmt.Println("发送完成")
}()
time.Sleep(5 * time.Second)
}
输出:
开始接收
开始发送
现象:观察到输出上面两行以后会停滞5秒,然后程序退出。
分析可知,当前条件下:
- 输出
开始接收
(开始发送
),没有输出接收完成
(发送完成
),可以知道:对空通道读写确实会发生阻塞 - 两个协程一个读、另一个写,都发生了阻塞,可以说明:空通道读写是无效操作,并不会真正的发送、接收。[3]
2、源码分析
既然没人能说明究竟是怎么回事,那就需要翻源码了。写这篇文章时,我阅读的是 1.17.6
版本的golang源码。
2.1 通道结构
通道相关的源码在 go/src/runtime/chan.go
中,chan
的本体是结构体 type hchan struct{...}
2.2 发送接口
通道的发送接口有3个调用链条:
func chansend1(c *hchan, elem unsafe.Pointer)
->func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr)
func selectnbsend(c *hchan, elem unsafe.Pointer)
->func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr)
func reflect_chansend(c *hchan, elem unsafe.Pointer, nb bool)
->func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr)
其中 chansend
是最底层的发送接口:
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
if c == nil { // channel 为nil时:
if !block { // 1. 当前以非阻塞模式发送:
return false // 直接返回false,表示通道已关闭或不可用
}
// 2. 当前为阻塞模式:
gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2) // 2.1 协程进入休眠状态
throw("unreachable") // panic
}
... ...
}
观察代码和注释,可以知道:
-
第一个链条
chansend1
对应形如<- chan
这种格式的操作。这种情况下
chansend
的参数block
始终为true
:func chansend1(c *hchan, elem unsafe.Pointer) { chansend(c, elem, true, getcallerpc()) }
因此只要通道是
nil
就会导致当前协程阻塞,并且不能解除阻塞,因为通道不能再次关闭,也无法发送数据。 -
第二个链条
selectnbsend
对应形如select {case _ <- chan}
这种格式的操作这种情况下
chansend
的参数block
始终为false
:func selectnbsend(c *hchan, elem unsafe.Pointer) (selected bool) { return chansend(c, elem, false, getcallerpc()) }
因此通道用
select
读数据会直接返回false
。 -
第三个链条
reflect_chansend
对应函数reflect.chansend
的操作这种情况下
chansend
的参数block
由外部输入,因此需要根据reflect
具体情况判断。
参考资料及附录
本文由 qingchuwudi 译制或原创,除非另有声明,在不与原著版权冲突的前提下,本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
空通道是指
channel
的值 为nil
,通常是channel
类型的变量没有初始化、已有的channel
关闭形成的。 ↩︎由
panic
导致的程序崩溃 ↩︎第2个结论表明 《Go题库·13》向为nil的channel发送数据会怎么样 里面的观点 “空通道即无缓冲通道。无缓冲通道上的发送操作将会阻塞,直到另一个goroutine在对应的通道上执行接收操作,这时值传送完成,两个goroutine都可以继续执行。” 是错误的。 ↩︎