当对未关闭的无缓冲通道使用 `for range` 时,循环会在所有数据读取完毕后持续阻塞等待新值,而发送方 goroutine 已退出且通道未关闭,最终引发“all goroutines are asleep”死锁。
在 Go 中,for v := range ch 语句具有明确的语义:它会持续接收通道 ch 中的值,直到该通道被显式关闭(close(ch))。一旦通道关闭,range 自动退出循环;若通道始终保持打开状态,即使所有发送 Goroutine 已完成并退出,range 仍会无限阻塞——因为 Go 运行时无法自动推断“不会再有新值发送”,它只依赖 close 信号作为终止依据。
你的原始代码中:
✅ 正确做法是:在确认所有发送操作完成后,由某个 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")
}⚠️ 注意事项:
总结:Go 的 for range 与通道生命周期强绑定,关闭通道是唯一合法的终止信号。理解这一设计原则,是写出健壮并发程序的关键基础。