向Go通道发送超过8个元素为何会引发死锁?
Go通道死锁问题分析与修复
问题根源
你的代码出现死锁的核心原因是:
printCountgoroutine会在读取到-1时退出循环,导致该goroutine终止。此时主goroutine还在继续向通道发送后续元素(3、4),而无缓冲通道的发送操作必须有接收方才能完成,没有接收方的发送会永久阻塞,最终触发死锁。- 原代码中用
time.Sleep做同步是不可靠的,它只能延迟程序退出,无法保证所有元素都被正确处理,当元素数量超过接收goroutine的处理逻辑时必然出问题。
解决方案
下面提供两种可靠的修复方案:
方案1:用通道关闭+WaitGroup同步(推荐)
这种方式是Go中处理goroutine同步的标准做法,通过关闭通道通知接收goroutine所有元素已发送完毕,用WaitGroup等待接收逻辑完成。
package main import ( "fmt" "sync" ) func printCount(c chan int, wg *sync.WaitGroup) { defer wg.Done() // 函数退出时标记WaitGroup任务完成 // 遍历通道,直到通道被关闭 for num := range c { fmt.Print(num, " ") } } func main() { a := []int{8, 6, 7, 5, 3, 0, 9, -1, 3, 4} c := make(chan int) var wg sync.WaitGroup wg.Add(1) // 注册1个等待任务 go printCount(c, &wg) // 发送所有元素到通道 for _, v := range a { c <- v } close(c) // 发送完毕后关闭通道,通知接收goroutine退出 wg.Wait() // 等待接收goroutine处理完所有元素 fmt.Println("\nEnd of main") }
方案2:保留“遇-1停止”逻辑+信号通道
如果需要保留原逻辑中“读取到-1就停止处理”的需求,可以通过额外的信号通道通知主goroutine停止发送后续元素,避免死锁。
package main import ( "fmt" "sync" ) func printCount(c chan int, exit chan struct{}, wg *sync.WaitGroup) { defer wg.Done() num := 0 for num >= 0 { select { case num = <-c: if num >= 0 { fmt.Print(num, " ") } case <-exit: // 收到退出信号时直接返回 return } } close(exit) // 读到-1后,关闭信号通道通知主goroutine停止发送 } func main() { a := []int{8, 6, 7, 5, 3, 0, 9, -1, 3, 4} c := make(chan int) exit := make(chan struct{}) var wg sync.WaitGroup wg.Add(1) go printCount(c, exit, &wg) for _, v := range a { select { case c <- v: if v == -1 { <-exit // 发送-1后,等待接收goroutine的退出信号 break } case <-exit: // 收到退出信号后停止发送 break } } wg.Wait() fmt.Println("\nEnd of main") }
关键知识点
- 无缓冲通道的发送和接收操作是同步的,必须同时有发送方和接收方才能完成,否则会阻塞。
- 不要依赖
time.Sleep做goroutine同步,应该使用sync.WaitGroup、通道关闭或信号通道这类可靠的同步机制。 - 通道关闭是安全的通知方式,接收方可以通过
range遍历或ok值判断通道是否关闭,从而优雅退出。
内容的提问来源于stack exchange,提问作者yagnik pokal
相关产品推荐
相关产品推荐

