跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

Go 中使用 for range 遍历未关闭通道导致死锁的解决方案

当对未关闭的无缓冲通道使用 `for range` 时,循环会在所有数据读取完毕后持续阻塞等待新值,而发送方 goroutine 已退出且通道未关闭,最终引发“all goroutines are asleep”死锁。 在 Go 中,for v := range ch 语句具有明确的语义:它会 持续接收通道 ch 中的值,直到该通道被显式关闭(close(ch)) 。一旦通道关闭,range 自动退出循环;若通道始终保持打开状态,即使所有发送 Goroutine 已完成并退出,range 仍会无限阻塞——因为 Go 运行时无法自动推断“不会再有新值发送”,它只依赖 close 信号作为终止依据。 你的原始代码中: 创建了无缓冲通道 my_channel := make(chan int) 启动 3 个 Goroutine 调用 sum_up(i, my_channel),分别向通道发送 1、3、6 主 Goroutine 执行 for ele := range my_channel —— 成功接收并打印全部 3 个值 但此时通道仍处于 打开且空闲状态 ,range 继续等待下一个值 所有 sum_up Goroutine 已执行完毕并退出,主 Goroutine 又未关闭通道,也无其他 Goroutine 可写入 最终所有 Goroutine(仅剩主 Goroutine 在阻塞等待)陷入休眠 → 触发 fatal deadlock ✅ 正确做法是: 在确认所有发送操作完成后,由某个 Goroutine 显式关闭通道 。常用模式是结合 sync.WaitGroup 等待所有发送者结束,再关闭通道:
package main import ( "fmt" "sync" ) func sum_up(my_int int, cs chan int, wg *sync.WaitGroup) { defer wg.Done() // 更简洁的收尾方式 my_sum := 0 for i := 0; i < my_int; i++ { my_sum += i } cs <- my_sum } func main() { var wg sync.WaitGroup my_channel := make(chan int) // 启动发送 Goroutine,并注册 WaitGroup 计数 for i := 2; i < 5; i++ { wg.Add(1) go sum_up(i, my_channel, &wg) } // 单独 Goroutine 等待全部发送完成,然后关闭通道 go func() { wg.Wait() close(my_channel) // 关键:通知 range 可以退出 }() // range 安全退出:收到所有值 + 通道关闭信号 for ele := range my_channel { fmt.Println(ele) } fmt.Println("Done") }
⚠️ 注意事项: 切勿在发送 Goroutine 中直接 close(ch) :多个 Goroutine 同时关闭同一通道会 panic(close of closed channel) WaitGroup 必须在启动 Goroutine 前调用 Add(),且 Done() 应确保执行(推荐 defer) 若使用带缓冲通道(如 make(chan int, 3)),问题本质不变——range 仍需 close 才能退出 替代方案:使用 select + done 通道实现超时或手动控制,但 close + range 是最符合 Go 惯用法的简洁解法 总结:Go 的 for range 与通道生命周期强绑定, 关闭通道是唯一合法的终止信号 。理解这一设计原则,是写出健壮并发程序的关键基础。

相关文章