fatal error: all goroutines are asleep - deadlock! 表示程序彻底停摆,唯一可靠线索是 panic 时输出的 goroutine 堆栈;需重点检查 main 卡在 channel 操作、wg.wait() 或 mu.lock() 的位置,其他 goroutine 是否全阻塞于同类同步原语,以及是否存在 select {} 或 init 依赖导致的隐式死锁。

看到 fatal error: all goroutines are asleep - deadlock! 就别查日志轮转、别加 time.Sleep、别改逻辑猜路径了——程序已彻底停摆,唯一可信线索就是 panic 时打印的 goroutine 堆栈。
看 panic 日志里每个 goroutine 卡在哪一行
Go 运行时在死锁时强制 dump 所有 goroutine 的当前调用栈,这不是辅助信息,是第一手证据。重点不是“哪行报错”,而是“谁在等什么”:
-
maingoroutine 停在ch 或 <code>?说明 channel 收发没配对;停在 <code>wg.Wait()?说明计数没归零;停在mu.Lock()?说明锁没被释放或 unlock 被跳过 - 其他 goroutine 是否全卡在
runtime.chansend(都在发)但没人出现在runtime.chanrecv?那就是发送方集体阻塞 - 有没有 goroutine 卡在
select {}或空for {}?那是退出条件缺失,不是资源争用,但照样触发死锁 - 堆栈里出现
init函数且带 goroutine 启动?小心跨包 init 依赖:A 包的 init 启 goroutine 等 B 包 init 完成,而 B 又等 A —— 永久卡住,不报 channel 相关关键词
用 GODEBUG=schedtrace=1000 快速确认是否全员阻塞
当 panic 日志被截断(比如跑在容器里 stdout 被吞),或者你怀疑输出不全,这个环境变量比翻堆栈更快定位“是不是真卡死了”:
- 命令是
GODEBUG=schedtrace=1000 go run main.go(注意不是GODEDEBUG) - 每秒输出一行类似
SCHED 00001ms: gomaxprocs=8 idle=0/8/0 runable=0 gc=0 active=1 [0 0 0 0 0 0 0 0]的快照 - 关键看末尾
goroutines: N和runnable: M:如果连续几秒都是goroutines: 1且runnable: 0,基本可断定只剩 main 在跑,其余全卡死 - 若
waiting: N长期不变而runnable为 0,就是典型死锁;若runnable > 0,说明还有活口,得查context.WithCancel是否漏 cancel、close(ch)是否遗漏、或wg.Add()和wg.Done()是否配对
go tool trace 查谁在等谁、等了多久
pprof 只能看到栈帧快照,而 go tool trace 能还原状态变迁,对“看似没死锁但程序不动”的情况更敏感:
- 代码开头加
import _ "net/http/pprof",main()里起服务:go http.ListenAndServe("localhost:6060", nil) - 运行后访问
http://localhost:6060/debug/trace?seconds=5下载trace.out - 执行
go tool trace trace.out,打开 Web UI,在 “Goroutine” 标签页筛选状态为chan send或chan recv的 goroutine - 典型线索:
chan recv状态持续超过 5 秒,且没有对应 sender 出现;注意无缓冲 channel 最容易暴露阻塞,带缓冲的需结合len(ch)和cap(ch)判断是否真满/空
检查 channel 收发是否成对、关闭是否唯一
80% 的死锁集中在 channel 基础约束被忽略:
- 向未启动接收 goroutine 的无缓冲
ch发送 → 立即死锁;for v := range ch前没人close(ch)→ 无限等待 - 单个 goroutine 对无缓冲 channel 先
ch 再 <code> → 必死锁;多个 goroutine 共用一个 <code>ch但由不同 goroutineclose(ch)→ 可能 panic 或漏数据 -
len(ch) == cap(ch)判断满、len(ch) == 0判断空,仅对有缓冲 channel 有效;无缓冲 channel 的len(ch)永远是 0,不能靠它判断可写性 - 必须确保只有一个 goroutine 负责
close(ch),且只 close 一次;接收方要用v, ok := 判断是否已关闭,不能只依赖 <code>range
最易被忽略的点:sync.RWMutex 在同一个 goroutine 中多次 RLock(),或已持读锁再调 Lock(),不会 panic,但会让其他 goroutine 永久等待——panic 日志里看不到 channel 关键词,只显示一堆卡在 (*RWMutex).RLock 的堆栈。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











