为何这段Go代码未产生死锁?关于无缓冲通道的疑问
测试代码
package main import ( "fmt" "sync" "time" ) var wg sync.WaitGroup func main() { ch := make(chan int) // 把通道声明在main()里 wg.Add(4) go sender(ch, 42) go receiver(ch) go sender(ch, 7) go receiver(ch) wg.Wait() } func receiver(ch <-chan int) { time.Sleep(5 * time.Second) i := <-ch fmt.Printf("RECEIVING %v \n", i) wg.Done() } func sender(ch chan<- int, i int) { ch <- i wg.Done() }
我的认知与疑惑
我对Go无缓冲通道的现有认知:
- 无缓冲通道的发送方会阻塞,直到接收方读取通道数据;
- 如果发送方在接收方读取第一条数据前尝试发送另一条消息,会无限阻塞进而导致死锁。
请问这个认知是否有误?
我原本认为上面的代码会触发死锁,理由是:
启动了2个sender goroutine(sender(ch, 42)和sender(ch,7))与2个receiver goroutine;每个sender会立即向无缓冲通道发值,但每个receiver要先等5秒才会读取通道。按逻辑,sender会因为没人接收而阻塞,最终引发死锁,但实际运行并没有死锁,想知道原因。
另外,我用下面的代码能复现死锁:
wg.Add(3) go sender(ch, 42) go receiver(ch) go sender(ch, 7)
问题解析
你的核心认知前半部分是对的,但后半部分有偏差——无缓冲通道的发送阻塞是针对单个发送操作的,只要有对应数量的接收操作最终会被执行,就不会死锁。
为什么第一段代码没触发死锁
你启动了2个sender和2个receiver,虽然receiver先睡5秒,但它们最终都会执行<-ch的接收操作。Go调度器会把阻塞的sender goroutine挂起,等5秒后receiver醒来执行接收时,每个sender的阻塞都会被对应的接收解除:一个receiver接收42,另一个接收7,所有goroutine都能完成wg.Done(),main里的wg.Wait()也能正常结束,自然不会死锁。
为什么第二段代码会触发死锁
这里是2个sender和1个receiver。第一个sender发送42,会阻塞到receiver醒来接收;但第二个sender发送7时,此时没有第二个receiver来接收它——唯一的receiver还在睡觉,就算等它醒来,接收完42后也没有后续的接收操作了。这个第二个sender会一直阻塞,永远无法执行wg.Done(),而wg.Add(3)要求3个Done才能结束,main的wg.Wait()会一直等,最终整个程序所有goroutine都卡住,触发死锁。
总结:无缓冲通道的发送阻塞,只要发送操作的数量 ≤ 接收操作的数量(且接收操作最终会被执行),就不会死锁;只有当发送操作比接收操作多,或者没有对应的接收操作时,才会出现永久阻塞进而死锁。
内容的提问来源于stack exchange,提问作者Shankhashuvro Sinha

