<p>fatal error: all goroutines are asleep - deadlock! 会强制打印所有 goroutine 堆栈,关键需分析哪些 goroutine 卡在 channel 操作上。</p>

看 panic 日志里的 goroutine 堆栈
Go 运行时一旦触发 fatal error: all goroutines are asleep - deadlock!,一定会打印所有 goroutine 的完整调用栈。这不是可选日志,是强制输出。关键就藏在这些堆栈里:哪几个 goroutine 卡在 ch 或 <code>,它们各自在哪个文件哪一行,有没有被 <code>select 包裹,是否在循环里。
常见错误现象:
- 主 goroutine 卡在
ch ,但堆栈里找不到任何其他 goroutine 在 <code> - 多个 goroutine 分别卡在不同 channel 上,比如 A 等
chA、B 等chB、C 又在等 A 和 B 的结果——形成等待环 - 一个 goroutine 卡在
for range ch,但堆栈里所有发送方都已退出或阻塞
实操建议:
- 用
go run main.go复现时,直接看终端输出;线上环境则必须保留 panic 前的完整日志(尤其是带 goroutine ID 和 stack trace 的部分) - 重点关注状态为
chan send或chan receive的 goroutine,忽略running或syscall状态的(它们还没死锁) - 如果堆栈里只有 1 个 goroutine,基本就是单 goroutine 对无缓冲 channel 先发后收(或先收后发),属于语法级错误
用 go tool pprof 抓运行中 goroutine 快照
不是所有死锁都会立刻 panic。有些服务“假死”:main 退出了,但后台还有 goroutine 在轮询、sleep 或做 IO,runtime 认为“还有人能干活”,就不报错。这时候得主动查。
立即学习“go语言免费学习笔记(深入)”;
实操建议:
- 启动程序时加
-gcflags="all=-l"避免内联,让 pprof 能准确定位到行号 - 用
curl http://localhost:6060/debug/pprof/goroutine?debug=2(需启用net/http/pprof)获取当前所有 goroutine 的阻塞点 - 重点搜
chan send、chan receive、select这些关键词,看哪些 channel 出现在多个 goroutine 的堆栈里 - 配合
go tool pprof -http=:8080 goroutine.svg可视化,一眼看出 goroutine 之间的等待关系
检查无缓冲 channel 的收发是否跨 goroutine
无缓冲 channel 的语义不是“存数据”,而是“同步交接”。发送和接收必须由**不同 goroutine** 同时执行,否则必然卡死。
常见错误场景:
-
ch := make(chan int)后直接ch ,没用 <code>go func()启动接收者 - 接收逻辑写在
if分支里,但条件不满足,导致没人真正执行 - 接收方在另一个包里,但初始化逻辑漏掉或 panic 退出,导致 goroutine 提前死亡
实操建议:
- 对每个无缓冲 channel,手动画两列:左边列所有可能执行
ch 的位置,右边列所有可能执行 <code> 的位置,确认它们是否必然落在不同 goroutine - 用
go vet -shadow检查变量遮蔽,避免本该启动 goroutine 的地方用了同名局部变量覆盖 - 在 channel 创建处加注释,例如:
// ch: used for sync between worker and main, must be non-blocking send/receive pair
用 select + default 或超时防止单点阻塞
单纯靠“有 goroutine 接收”不能保证不死锁。比如接收方处理慢、panic、或提前 return,发送方仍会永久等待。这时候需要防御性设计。
实操建议:
- 不要依赖“一定有人收”,改用
select控制等待行为: select { case chselect { case v :=- 对必须保证送达的场景,用带缓冲的 channel(容量 ≥ 最大并发发送数),并配
len(ch) == cap(ch)判断是否已满,而不是只靠 panic
真正难排查的从来不是 panic 那一刻,而是 panic 之前那个 goroutine 已经静默退出、没人再消费 channel 的瞬间。这时候堆栈里只剩发送方在等,而接收方连 goroutine ID 都没了。盯住 goroutine 生命周期,比盯住 channel 本身更重要。


















