Go协程向多通道写入时触发死锁(Fatal error: all goroutines are asleep deadlock)问题求助
Go协程向多通道写入时触发死锁(Fatal error: all goroutines are asleep deadlock)问题求助
兄弟,我看了你的代码,问题出在无缓冲通道的阻塞特性和WaitGroup的调用顺序上,咱们一步步拆解清楚:
核心问题分析
你创建的evenChannel和oddChannel是无缓冲通道(make(chan int)默认就是无缓冲),这种通道的发送操作会一直阻塞,直到有接收者准备好接收数据。但你的main函数逻辑是:
- 先启动生产者协程
generateNumbers - 立刻调用
wg.Wait()等待生产者完成 - 等生产者完成后才开始接收通道数据
这就导致了死锁:生产者协程在往通道写第一个数的时候,因为没有任何接收者,直接被阻塞住,永远执行不到defer wg.Done();而main函数卡在wg.Wait()这里,永远等不到信号,最终所有协程都休眠,触发死锁错误。
另外,你原代码里关闭通道的位置也不对——通道应该由生产者在写完所有数据后关闭,告诉消费者“没有更多数据了”,而不是在消费者接收完之后才关闭。
修改后的解决方案
这里给你两种常用的写法,都能解决问题:
方案一:用独立消费者协程 + WaitGroup(清晰直观)
package main import ( "fmt" "sync" ) func generateNumbers(evenChannel, oddChannel chan<- int) { for i := 1; i <= 50; i++ { if i%2 == 1 { oddChannel <- i } else { evenChannel <- i } } // 生产者写完所有数据后,主动关闭通道 close(evenChannel) close(oddChannel) } // 单独的偶数处理协程 func printEven(wg *sync.WaitGroup, evenChannel <-chan int) { defer wg.Done() // range遍历通道,通道关闭后会自动退出循环 for evenNum := range evenChannel { fmt.Println("Even Number:", evenNum) } } // 单独的奇数处理协程 func printOdd(wg *sync.WaitGroup, oddChannel <-chan int) { defer wg.Done() for oddNum := range oddChannel { fmt.Println("Odd Number:", oddNum) } } func main() { var wg sync.WaitGroup evenChannel := make(chan int) oddChannel := make(chan int) // 启动生产者协程 go generateNumbers(evenChannel, oddChannel) // 启动两个消费者协程,等待它们处理完成 wg.Add(2) go printEven(&wg, evenChannel) go printOdd(&wg, oddChannel) // 等待所有消费者处理完毕 wg.Wait() }
方案二:用匿名协程接收 + 信号通道(无需额外函数)
如果不想写单独的消费者函数,也可以用匿名协程配合信号通道来实现:
package main import ( "fmt" ) func generateNumbers(evenChannel, oddChannel chan<- int) { for i := 1; i <= 50; i++ { if i%2 == 1 { oddChannel <- i } else { evenChannel <- i } } close(evenChannel) close(oddChannel) } func main() { evenChannel := make(chan int) oddChannel := make(chan int) go generateNumbers(evenChannel, oddChannel) // 用两个信号通道标记接收完成 evenDone := make(chan struct{}) oddDone := make(chan struct{}) // 匿名协程处理偶数 go func() { for num := range evenChannel { fmt.Println("Even Number:", num) } evenDone <- struct{}{} }() // 匿名协程处理奇数 go func() { for num := range oddChannel { fmt.Println("Odd Number:", num) } oddDone <- struct{}{} }() // 等待两个接收任务都完成 <-evenDone <-oddDone }
关键总结
- 无缓冲通道的发送和接收必须同时准备好,否则会阻塞,所以一定要让接收逻辑和发送逻辑并发执行,不能等发送完再接收。
- 通道的关闭职责属于生产者,写完数据后关闭通道,消费者才能通过
range或者ok判断知道没有更多数据了。
备注:内容来源于stack exchange,提问作者Pikachu
相关产品推荐
相关产品推荐

