死锁是程序逻辑彻底走不下去,Go 运行时检测到所有 goroutine 均阻塞且无法唤醒时立即 panic;典型场景如无缓冲 channel 未被接收或发送即阻塞。

看到 fatal error: all goroutines are asleep - deadlock! 就是真死锁,不是卡顿
Go 运行时一旦发现所有 goroutine 全部阻塞、且没有任何一个能被唤醒(比如没人收 channel、没人放锁、没人关通道),会立刻 panic 并打印这句错误——它不等你按 Ctrl+C,也不给你留日志机会。这不是性能问题,是程序逻辑彻底走不下去了。
- 典型场景:
ch := make(chan int)后直接ch ,但没开 goroutine 接收;或 <code>for range ch却忘了close(ch) - 注意:哪怕只差一个 goroutine 在运行,就不会触发这个 panic;所以没报这错 ≠ 没问题,可能只是“活锁”或资源争用
- 别在测试里靠
time.Sleep等 goroutine 完成——它掩盖死锁,还让 CI 偶发失败
用 GODEBUG=schedtrace=1000 快速确认是否真卡在调度器上
加这个环境变量后,Go 每秒输出一次调度摘要,能一眼看出 goroutine 数量是否归零、有没有长期处于 runnable 或 waiting 状态的协程。
- 命令示例:
GODEDEBUG=schedtrace=1000 go run main.go(注意是GODEBUG,不是GODEDEBUG) - 关键看输出末尾的
SCHED行:如果goroutines: 1且持续多秒不变,而你的程序本该有多个 goroutine,大概率主 goroutine 卡在某处,其他已退出 - 配合
go tool trace可视化更准,但日常排查,schedtrace已足够快准狠
pprof 查不到这个死锁,但能帮你排除“伪死锁”
/debug/pprof/goroutine?debug=2 返回的是当前活着的 goroutine 堆栈;而 all goroutines are asleep 发生时,runtime 已经 panic,根本进不了 pprof 的 HTTP handler。
- 如果你的程序没 panic 却长时间无响应,才轮到 pprof 上场:启动
http.ListenAndServe("localhost:6060", nil)+import _ "net/http/pprof",再访问http://localhost:6060/debug/pprof/goroutine?debug=2 - 重点找重复出现的阻塞点,比如大量 goroutine 停在
chan send或sync.(*Mutex).Lock—— 这说明有人拿了资源不放,不是死锁,是锁滥用 - 若看到一堆
runtime.gopark,别急着翻源码,先查是不是 channel 没 close、context 被提前 cancel、或 WaitGroup 计数没对上
最常踩的坑:range channel 不关、WaitGroup Add/Wait 顺序错、init 里起 goroutine
这三个地方占了线上死锁问题的八成以上,而且错误非常隐蔽。
立即学习“go语言免费学习笔记(深入)”;
-
for v := range ch必须配close(ch),且只能由发送方关闭;Walk 类递归函数尤其容易漏掉这一句 -
wg.Add(1)必须在go func()之前调用,否则可能漏加;defer wg.Done()必须放在 goroutine 函数第一行,防止 panic 导致计数不减 - 包级
init()函数里起 goroutine 是高危操作——init 是单线程同步执行的,goroutine 里若依赖另一个包的 init 结果,就会永久卡住
真正难的不是定位,而是理解“谁在等”背后“谁没放手”。channel 死锁往往不是某一行写错了,而是整条数据流的责任边界模糊了:谁发、谁收、谁关、谁等,缺一不可。










