Go运行报all goroutines are asleep死锁错误如何解决
Go通道死锁问题分析与修复
基础信息
- 运行环境:go1.17.8 darwin/arm64,M1芯片Mac,macOS Monterey 12.4 (21F79)
- 报错信息:
fatal error: all goroutines are asleep - deadlock! - 代码预期目标:通过两个协程交替打印0-9的奇偶数列
死锁成因
代码通过两个无缓冲通道A、B实现协程同步:主协程发送启动信号到A,偶数协程读A后打印偶数、发送信号到B,奇数协程读B后打印奇数、回传信号到A,最终通过WaitGroup等待两个协程退出。
死锁触发逻辑在流程最后一步:
- 偶数协程总共执行5次循环(打印0/2/4/6/8),仅需要从A读取5次信号,完成最后一次打印(数值8)、向B发送信号后,就会退出循环执行
wg.Done(),不会再读取A通道的信号。 - 奇数协程拿到B通道的最后一个信号后,打印最后一个奇数9,按照原有逻辑会向A写一个回执信号。但此时偶数协程已经退出,没有任何协程接收A通道的写入,奇数协程会直接阻塞在
A <- true这行,永远无法执行wg.Done()。 - 此时WaitGroup的计数永远剩余1,主协程阻塞在
wg.Wait(),所有协程都处于等待状态,直接触发死锁报错。
修复方案
核心是移除最后一次多余的、无对应接收方的通道写入:奇数协程打印完最后一个数字后,不需要再向A发送回执信号,直接退出循环即可。
修复后的完整可运行代码:
package main import ( "fmt" "sync" ) func main() { A := make(chan bool) B := make(chan bool) defer close(A) defer close(B) var wg sync.WaitGroup wg.Add(2) go func() { for i := 0; i < 10; i += 2 { if <-A { fmt.Println(i) B <- true } } wg.Done() }() go func() { for i := 1; i < 10; i += 2 { if <-B { fmt.Println(i) // 最后一次打印后无需回传信号,避免无接收方导致阻塞 if i+2 < 10 { A <- true } } } wg.Done() }() A <- true wg.Wait() }
运行后会按顺序输出0-9,无死锁问题。如果需要调整打印的数字上限,只需要修改两个循环的判断阈值,同时保证最后一个执行业务逻辑的协程不再回传通道信号即可。
内容的提问来源于stack exchange,提问作者Nemo
相关产品推荐
相关产品推荐

