死锁时程序彻底停摆,需依据panic日志中的goroutine堆栈分析:重点查看main goroutine是否卡在mu.Lock()、wg.Wait()等同步原语上。

看到 fatal error: all goroutines are asleep - deadlock! 就是真死锁,不是卡顿、不是慢、不是资源争用——程序已彻底停摆,必须从 panic 日志和 goroutine 状态入手,而不是猜逻辑。
看 panic 日志里的 goroutine 堆栈
Go 运行时在死锁时会输出所有 goroutine 的当前调用栈,这是第一手线索。重点盯三类信息:
-
maingoroutine 停在哪一行?比如卡在mu.Lock()、wg.Wait()或 - 其他 goroutine 是否全卡在同一个 channel 操作(如都在
chan receive)或同一把锁上?说明资源被单点垄断 - 有没有 goroutine 卡在
select {}或空for循环里?那是典型的“忘了退出条件”
如果日志被截断,加环境变量运行:GODEBUG=schedtrace=1000 go run main.go,每秒输出调度快照,观察 goroutines: 数量是否长期为 0 —— 是,就确认全部阻塞。
用 go tool trace 定位阻塞点
pprof 只能看到栈帧,而 go tool trace 能还原 goroutine 状态变迁,一眼看出谁在等谁、等了多久、是否永久阻塞:
- 启动前加
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 receive或chan send的 goroutine
典型线索:某 goroutine 长时间处于 chan recv 状态,且无对应 sender 唤醒,基本就是死锁源头。注意:无缓冲 channel 最容易暴露阻塞;带缓冲的 channel 可能只是“暂时满/空”,需结合上下文判断。
查 channel 收发是否成对且有关闭方
无缓冲 channel 是死锁高发区:发送前必须有接收方就位,否则发送方永久阻塞;for v := range ch 前必须有人 close(ch),否则无限等待:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 向未启动接收 goroutine 的
channel发送 → 立即死锁 - 多个 goroutine 共用一个
channel,但由不同 goroutine 关闭 → 可能 panic(send on closed channel)或漏数据 -
len(ch) == cap(ch)判断满、len(ch) == 0判断空,仅对有缓冲channel有效;无缓冲channel无法用长度判断可读/可写 - 必须确保只有一个 goroutine 负责
close(ch),且只close一次;接收方要配合v, ok := 判断是否已关闭
临时规避盲等:用 select { case v := 防阻塞,但别让 default 空转——高频轮询会掩盖问题,还拖慢调度。
别被 sync.Mutex 假象骗了
sync.Mutex 不报 all goroutines are asleep,但它卡住时 goroutine 仍处于 running 状态,只是自己锁死了:
- 同一个 goroutine 连续两次
mu.Lock()(没Unlock)→ 直接卡死 -
defer mu.Unlock()写在if分支里,或提前return漏掉 → 锁永远不释放 - goroutine A 先锁
mu1再锁mu2,B 反过来先mu2后mu1→ 经典循环等待 - 持有锁期间调用可能阻塞的操作(如写
channel、HTTP 请求)→ 锁时间拉长,增加冲突概率
pprof 的 /debug/pprof/block 和 /debug/pprof/mutex 能帮你定位这类“伪死锁”,但得先让程序活过 panic 阶段——也就是说,它不适用于 all goroutines are asleep 场景,只适用于还有 goroutine 在跑、但逻辑卡住的情况。
最常踩的坑是:for range ch 忘关、WaitGroup Add/Wait 顺序错、init 里起 goroutine——这三个地方占了线上死锁问题的八成以上,而且错误非常隐蔽。真正难的不是发现死锁,而是确认那个看似“正常”的 goroutine 其实正卡在锁里,却没触发 runtime 的全局阻塞检测。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










