
当对未关闭的无缓冲通道使用 `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 与通道生命周期强绑定,关闭通道是唯一合法的终止信号。理解这一设计原则,是写出健壮并发程序的关键基础。









