Go协程死锁排查:用WaitGroup仍休眠死锁且无法持续通信
Go并发通道死锁问题分析
我正在学习Go并发编程,希望两个goroutine通过通道持续传递更新后的值:一个goroutine对数值加2,另一个减1。但运行以下代码后出现死锁错误,协程无法持续交互,即使未调用wg.Done()也无法无限循环,请问代码存在什么问题?
问题代码
package main import ( "fmt" "sync" "time" ) var wg sync.WaitGroup func addTwo(r chan int, e chan int) { val := <-r fmt.Println("AT Recieved: ", val) time.Sleep(1 * time.Second) e <- val + 2 } func subtractOne(r chan int, e chan int) { val := <-r fmt.Println("SO Recieved: ", val) time.Sleep(1 * time.Second) e <- val - 1 } func main() { ch := make(chan int) ch2 := make(chan int) wg.Add(1) go addTwo(ch, ch2) wg.Add(1) go subtractOne(ch2, ch) ch <- 1 wg.Wait() }
运行输出
AT Recieved: 1 SO Recieved: 3 fatal error: all goroutines are asleep - deadlock! goroutine 1 [semacquire]: sync.runtime_Semacquire(0x4b2de8?) /usr/lib/go-1.18/src/runtime/sema.go:56 +0x25 sync.(*WaitGroup).Wait(0x0?) /usr/lib/go-1.18/src/sync/waitgroup.go:136 +0x52 main.main()
问题原因分析
- goroutine仅执行单次逻辑就终止:
addTwo和subtractOne函数只完成一次接收、处理、发送操作就退出,没有循环逻辑支撑持续交互。 - 通道阻塞触发死锁:
- 主goroutine向
ch发送初始值1后,addTwo接收并处理为3,发送到ch2; subtractOne接收3并处理为2,发送到ch;- 此时两个业务goroutine已全部执行完毕退出,
ch中的2无goroutine接收,主goroutine卡在wg.Wait(),所有goroutine进入等待状态,触发死锁。
- 主goroutine向
- WaitGroup未正确使用:两个goroutine都未调用
wg.Done(),但这并非死锁直接原因,只是让主goroutine持续等待,最终还是因通道阻塞和goroutine退出触发死锁。
修复方案
要实现持续交互,需给goroutine添加循环逻辑,同时通过控制通道实现优雅终止:
package main import ( "fmt" "sync" "time" ) var wg sync.WaitGroup func addTwo(r chan int, e chan int, stop chan struct{}) { defer wg.Done() for { select { case val := <-r: fmt.Println("AT Recieved: ", val) time.Sleep(1 * time.Second) e <- val + 2 case <-stop: fmt.Println("addTwo goroutine exiting") return } } } func subtractOne(r chan int, e chan int, stop chan struct{}) { defer wg.Done() for { select { case val := <-r: fmt.Println("SO Recieved: ", val) time.Sleep(1 * time.Second) e <- val - 1 case <-stop: fmt.Println("subtractOne goroutine exiting") return } } } func main() { ch := make(chan int) ch2 := make(chan int) stop := make(chan struct{}) wg.Add(2) go addTwo(ch, ch2, stop) go subtractOne(ch2, ch, stop) // 发送初始值启动交互 ch <- 1 // 控制运行时长后终止程序 time.Sleep(5 * time.Second) close(stop) wg.Wait() fmt.Println("Main goroutine exiting") }
修复说明
- 添加
for循环让goroutine持续监听通道,处理数值传递逻辑; - 使用
select结合stop通道,实现goroutine的优雅终止,避免无限运行; - 每个goroutine添加
defer wg.Done(),确保WaitGroup能正确等待所有goroutine退出; - 主goroutine通过
time.Sleep控制交互时长,关闭stop通道触发goroutine退出。
内容的提问来源于stack exchange,提问作者Om Gupta
相关产品推荐
相关产品推荐

