Go协程代码触发Deadlock,请求协助定位问题原因
Go Goroutine 管道死锁问题排查与修复
问题代码
package main import "fmt" func main() { // create & consume channel counter := make(chan int) even := make(chan int) odd := make(chan int) square := make(chan int) merge := make(chan int) out := make(chan struct{}) //GoRoutines go counterFn(counter) go squarerFn(counter, square) go counterSplit(counter, even, odd) go merger(square, odd, merge) // 此处参数传递错误 go printOut(merge, out) <-out } func counterFn(counter chan int) { for i := 0; i < 5; i++ { counter <- i } close(counter) } func squarerFn(counter chan int, square chan int) { for i := range counter { square <- i * i } close(square) } func counterSplit(counter chan int, even chan int, odd chan int) { for i := range counter { if i%2 == 0 { even <- i } else { odd <- i } } close(even) close(odd) } func merger(even chan int, odd chan int, merge chan int) { i := 0 for { fmt.Printf("%d \n", i) a, ok := <-even if !ok { i++ } else { merge <- a } a, ok = <-odd if !ok { i++ } else { merge <- a } if i == 2 { break } } close(merge) } func printOut(merge chan int, out chan struct{}) { for i := range merge { fmt.Print(i) } close(out) }
错误输出
go run channel_pipelineAdv.go 0 0 , 0 1 0 , 9 fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive]: main.main() D:/channel_pipelineAdv.go:20 +0x233 goroutine 7 [chan send]: main.squarerFn(0x0?, 0x0?) D:/channel_pipelineAdv.go:32 +0x45 created by main.main in goroutine 1 D:/channel_pipelineAdv.go:16 +0x114 goroutine 8 [chan send]: main.counterSplit(0x0?, 0x0?, 0x0?) D:/channel_pipelineAdv.go:40 +0x5e created by main.main in goroutine 1 D:/channel_pipelineAdv.go:17 +0x179 goroutine 9 [chan receive]: main.merger(0x0?, 0x0?, 0x0?) D:/channel_pipelineAdv.go:59 +0xca created by main.main in goroutine 1 D:/channel_pipelineAdv.go:18 +0x1d9 goroutine 10 [chan receive]: main.printOut(0x0?, 0x0?) D:/channel_pipelineAdv.go:73 +0x77 created by main.main in goroutine 1 D:/channel_pipelineAdv.go:19 +0x227 exit status 2
问题定位
参数传递错误:
main中调用merger时,错误地将square通道作为第一个参数传入,而merger函数定义的第一个参数是even通道。这导致:even通道的数据没有任何goroutine接收,counterSplit在向even发送偶数时会永久阻塞merger一直在读取square和odd通道,但counterSplit阻塞后无法向odd发送更多数据,merger读取odd时会阻塞
无缓冲通道阻塞连锁反应:所有通道都是无缓冲的,发送操作必须等待接收操作完成。当
counterSplit阻塞在even发送时,无法继续接收counter通道的数据,导致counterFn后续的发送操作也阻塞;同时squarerFn在向square发送数据时,因merger阻塞在odd读取,无法接收square的数据,也会进入阻塞状态。最终所有goroutine都陷入等待,触发死锁。merger循环逻辑缺陷:
merger的循环采用顺序读取even和odd的方式,若其中一个通道无数据且未关闭,会直接阻塞。这种方式无法正确处理两个通道的异步数据。
修复方案
- 修正参数传递:将
merger的第一个参数改为even通道,确保counterSplit发送的偶数能被正常接收。 - 优化merger循环逻辑:使用
select语句同时监听两个通道,配合通道关闭状态判断,确保所有数据被读取完成后再退出循环。
修正后的完整代码
package main import "fmt" func main() { counter := make(chan int) even := make(chan int) odd := make(chan int) square := make(chan int) merge := make(chan int) out := make(chan struct{}) go counterFn(counter) go squarerFn(counter, square) go counterSplit(counter, even, odd) go merger(even, odd, merge) // 修正参数:传入even而非square go printOut(merge, out) <-out } func counterFn(counter chan int) { for i := 0; i < 5; i++ { counter <- i } close(counter) } func squarerFn(counter chan int, square chan int) { for i := range counter { square <- i * i } close(square) } func counterSplit(counter chan int, even chan int, odd chan int) { for i := range counter { if i%2 == 0 { even <- i } else { odd <- i } } close(even) close(odd) } func merger(even chan int, odd chan int, merge chan int) { evenClosed := false oddClosed := false for !evenClosed || !oddClosed { select { case val, ok := <-even: if ok { merge <- val } else { evenClosed = true } case val, ok := <-odd: if ok { merge <- val } else { oddClosed = true } } } close(merge) } func printOut(merge chan int, out chan struct{}) { for i := range merge { fmt.Printf("%d ", i) } close(out) }
说明
修正后,counterSplit发送的偶数能被merger正常接收,merger通过select异步处理两个通道的数据,所有goroutine都能正常执行完毕,不会触发死锁。
内容的提问来源于stack exchange,提问作者Monicka Akilan
相关产品推荐
相关产品推荐

