Go协程触发死锁的根因排查与修复方案咨询
死锁根因分析
你的代码触发死锁的核心问题是单个WaitGroup被错误地跨流水线多阶段复用,导致通道关闭逻辑与goroutine退出逻辑形成循环等待:
- 负责关闭
senderToService的匿名协程调用wg.Wait()时,会等待所有已添加到wg的goroutine(4个sender + 1个service + 1个receiver)完成,但service goroutine需要senderToService关闭才会退出,形成第一个循环:senderToService关闭等待wg计数归0 → wg计数归0等待service退出 → service退出等待senderToService关闭。 - 同理,负责关闭
serviceToReceiver的匿名协程也需要等待wg计数归0,但receiver goroutine需要serviceToReceiver关闭才会退出,形成第二个循环等待。 - 主goroutine的
wg.Wait()同样会一直等待所有goroutine完成,最终所有goroutine都陷入阻塞,触发死锁。
修复方案
拆分WaitGroup,让每个流水线阶段使用独立的WaitGroup管理,确保通道关闭逻辑只依赖当前阶段的完成:
package main import ( "fmt" "sync" ) func sender(id int, data []int, out chan<- [2]int, wg *sync.WaitGroup) { defer wg.Done() for i := 0; i < len(data); i += 2 { out <- [2]int{data[i], data[i+1]} } } func service(in <-chan [2]int, c, d int, out chan<- int, wg *sync.WaitGroup) { defer wg.Done() for pair := range in { sum := pair[0] + pair[1] if sum >= c && sum <= d { out <- sum } } } func receiver(in <-chan int, wg *sync.WaitGroup) { defer wg.Done() line := 1 for sum := range in { fmt.Printf("Line %d: Received sum %d\n", line, sum) line++ } } func main() { data1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} data2 := []int{11, 12, 13, 14, 15, 16, 17, 18, 19, 20} data3 := []int{21, 22, 23, 24, 25, 26, 27, 28, 29, 30} data4 := []int{31, 32, 33, 34, 35, 36, 37, 38, 39, 40} c, d := 15, 50 senderToService := make(chan [2]int, 10) serviceToReceiver := make(chan int, 10) // 拆分三个阶段的WaitGroup,各自管理对应阶段的goroutine var senderWG sync.WaitGroup var serviceWG sync.WaitGroup var receiverWG sync.WaitGroup // 启动sender阶段goroutine senderWG.Add(4) go sender(1, data1, senderToService, &senderWG) go sender(2, data2, senderToService, &senderWG) go sender(3, data3, senderToService, &senderWG) go sender(4, data4, senderToService, &senderWG) // 仅等待sender阶段完成后,关闭上游通道 go func() { senderWG.Wait() close(senderToService) }() // 启动service阶段goroutine serviceWG.Add(1) go service(senderToService, c, d, serviceToReceiver, &serviceWG) // 仅等待service阶段完成后,关闭中间通道 go func() { serviceWG.Wait() close(serviceToReceiver) }() // 启动receiver阶段goroutine receiverWG.Add(1) go receiver(serviceToReceiver, &receiverWG) // 等待最后一个阶段完成,结束程序 receiverWG.Wait() }
补充说明
- 流水线模式中,每个阶段的生命周期应独立:上游阶段完成后关闭输出通道,下游阶段通过监听通道关闭判断上游完成,进而退出自身。
- 单个WaitGroup适合管理同一阶段的多个goroutine,跨阶段复用会导致同步逻辑混乱,引发循环等待。
内容的提问来源于stack exchange,提问作者Mirama Khytyn
相关产品推荐
相关产品推荐

