
本文详解 go 程序中因未正确关闭通道导致的“all goroutines are asleep - deadlock”错误,通过分析典型代码案例,说明死锁成因,并提供符合 go 通道语义的标准修复方案。
本文详解 go 程序中因未正确关闭通道导致的“all goroutines are asleep - deadlock”错误,通过分析典型代码案例,说明死锁成因,并提供符合 go 通道语义的标准修复方案。
在 Go 并发编程中,通道(channel)是 goroutine 间通信的核心机制,但其使用需严格遵循“发送端负责关闭、接收端检查关闭状态”的约定。原始代码中,ch := make(chan int) 创建的是一个无缓冲通道,这意味着每次发送操作 都必须有对应的接收操作同时进行,否则发送 goroutine 将永久阻塞。
观察原程序执行流程:
- 主 goroutine 启动一个匿名 goroutine 向
ch发送 0~9 共 10 个整数; - 主 goroutine 自身进入
for { 循环接收数据; - 由于未显式关闭通道,当发送 goroutine 完成 10 次发送后自然退出,但主 goroutine 仍在等待下一个值——此时无任何 goroutine 能向
ch发送数据,也无人关闭通道,接收操作无限阻塞; - Go 运行时检测到所有 goroutine(仅剩主 goroutine)均处于阻塞状态且无法被唤醒,触发 fatal error:
all goroutines are asleep - deadlock!
✅ 正确解法是:由发送方在完成所有发送后调用 close(ch),使接收方能通过第二个返回值 ok 感知通道已关闭:
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch) // ✅ 关键:发送完毕后立即关闭通道
}()
// 接收端使用 range 或带 ok 检查的循环
for num := range ch { // 等价于 for { num, ok := <-ch; if !ok { break }; ... }
fmt.Println(num)
}
// 不再需要 time.Sleep 和手动 close(ch)
}⚠️ 注意事项:
-
切勿在接收端或主 goroutine 中调用
close(ch)—— 只有发送方有权关闭通道,否则可能引发 panic; -
close(ch)后仍可安全接收:已缓存的数据会被依次取出,之后每次接收返回零值(如0, false); - 使用
range ch是更简洁、惯用的接收方式,它自动处理通道关闭逻辑,无需手动判断ok; - 移除原代码末尾的
time.Sleep(2 * time.Second)和close(ch),它们不仅冗余,还违反通道所有权原则。
总结:Go 的死锁往往源于通道生命周期管理失当。牢记「谁发送,谁关闭;谁接收,谁检查」这一原则,结合 range 循环和 close() 的协同使用,即可写出健壮、无死锁的并发代码。

















