请求分析Go语言给定代码中存在死锁可能性的原因
先直接点出核心问题:你的代码里发送方只发送了一次数据,但接收方尝试了两次接收,再结合无缓冲通道的特性,最终导致了死锁。咱们一步步拆解细节:
1. 发送端的逻辑局限
你在main函数里只启动了两个发送goroutine:
go reuters(reutersCh) // 仅往reutersCh发送1次"REUTERS" go bloomberg(bloombergCh) // 仅往bloombergCh发送1次"BLOOMBERG"
这两个goroutine发送完数据就会直接退出,不会再产生任何新的发送操作。而reutersCh和bloombergCh都是无缓冲通道——无缓冲通道的核心规则是:发送和接收必须同时配对完成,否则操作会一直阻塞。
2. 接收端的重复调用触发死锁
你连续同步调用了两次newsReader(reutersCh, bloombergCh):
- 第一次调用
newsReader时,内部启动的两个匿名goroutine会分别尝试从两个通道接收数据。假设其中一个先拿到数据(比如reutersCh的内容),并把数据发到内部缓冲通道ch,随后x := <-ch拿到数据完成打印,第一次newsReader顺利执行完毕。此时另一个匿名goroutine也已经完成了对应通道的接收(因为发送方已经发了数据),并把内容存入了缓冲通道ch,但这个数据会因为newsReader退出而无人处理——不过这还不是死锁的直接原因。
真正的死锁发生在第二次调用newsReader:
第二次调用时,内部再次启动两个匿名goroutine,尝试从reutersCh和bloombergCh接收数据,但这两个通道的发送方早就已经发完数据退出了,没有任何goroutine会再往这两个通道发送内容。这两个匿名goroutine会永远阻塞在<-reutersCh和<-bloombergCh的接收操作上。
同时,main函数是同步调用newsReader的,它会一直等待第二次newsReader执行完毕,但newsReader里的x := <-ch永远拿不到数据(因为那两个接收goroutine都阻塞了,根本没法往ch里发数据),所以main也会陷入阻塞。整个程序的所有goroutine都无法继续执行,完全陷入阻塞状态,就触发了死锁。
3. 验证:只调用一次newsReader会怎样?
如果你把main里的第二次newsReader调用删掉,程序就能正常运行——发送端的一次发送刚好匹配接收端的一次接收,所有goroutine都能正常退出,不会出现阻塞。
4. 简单修复方案
如果需要多次调用newsReader,你需要保证每次接收都有对应的发送操作:
- 方案一:每次调用
newsReader时,都启动对应的发送goroutine
func main() { reutersCh := make(chan string) bloombergCh := make(chan string) // 第一次调用对应一次发送 go reuters(reutersCh) go bloomberg(bloombergCh) newsReader(reutersCh, bloombergCh) // 第二次调用再启动一次发送 go reuters(reutersCh) go bloomberg(bloombergCh) newsReader(reutersCh, bloombergCh) }
- 方案二:把发送端改成持续发送的模式(比如用循环),适配多次接收的场景
这样就能保证发送和接收的次数匹配,避免出现阻塞死锁的情况。
内容的提问来源于stack exchange,提问作者Stefan Hristov

