死锁发生时程序立即终止,不触发defer、无堆栈回溯,唯一线索是panic日志末尾的goroutine调用栈;看到“fatal error: all goroutines are asleep - deadlock!”需立即分析main goroutine卡点,如卡在ch。

死锁发生时程序立刻终止,没有堆栈回溯、不触发 defer、无法靠日志“猜”——必须从 panic 输出的 goroutine 列表里直接定位卡点。
看到 fatal error: all goroutines are asleep - deadlock! 就该停手查堆栈
这不是卡顿,是 runtime 确认无人可唤醒后强制退出。panic 日志末尾会列出所有 goroutine 的当前调用栈,这是唯一可信线索。
- 先盯
maingoroutine 停在哪:如果卡在ch 、<code>、<code>wg.Wait()或mu.Lock(),问题大概率就在这行 - 再扫其他 goroutine:是否全卡在
runtime.chansend或runtime.gopark+chan receive?说明发送没人收,或接收没人发 - 注意 init 里起的 goroutine:如果堆栈显示某个包的
init启动了 goroutine,又依赖另一个包的 init 完成,就会静默卡死,不报 channel 相关信息
pprof /goroutine 在死锁后根本用不上
死锁 panic 发生时 HTTP server 还没机会响应请求,/debug/pprof/goroutine 接口压根访问不到。它只适用于“程序没崩但不动了”的场景。
- 想用 pprof,必须提前加:
import _ "net/http/pprof"和go http.ListenAndServe("localhost:6060", nil) - 卡住但没 panic 时,立刻执行:
curl 'http://localhost:6060/debug/pprof/goroutine?debug=2' - 重点过滤含
chan send、chan receive、sync.(*Mutex).Lock的行;/debug/pprof/block对死锁帮助很小,它统计的是“耗时阻塞”,不是“永久阻塞”
go tool trace 比 pprof 更早暴露 channel 阻塞源头
pprof 只看快照,trace 能还原状态变迁——谁在等谁、等了多久、有没有被唤醒过。
- 启动前确保已导入
import _ "net/http/pprof",main 开头加:go http.ListenAndServe("localhost:6060", nil) - 运行后执行:
go tool trace http://localhost:6060/debug/trace?seconds=5,下载trace.out - 执行
go tool trace trace.out,打开 Web UI → “Goroutine” 标签页 → 筛选状态为chan recv或chan send - 典型死锁线索:某个 goroutine 长时间处于
chan recv,且没有对应 sender 出现;无缓冲 channel 最容易暴露这种问题
mutex 卡死不会触发 deadlock panic,但更难发现
sync.Mutex 不支持重入,也没递归检测。goroutine 在持有锁时再次调用 mu.Lock(),会静默停在 sync.runtime_SemacquireMutex,runtime 不认为这是“全部阻塞”,所以不 panic。
- 常见误用:
A()调mu.Lock(),然后调同结构体的B(),而B()内部又mu.Lock()—— 中间没mu.Unlock() -
sync.RWMutex的RUnlock()必须和RLock()成对,多调或少调都会破坏计数器,后续Lock()可能永远等不到 - 这类问题只能靠
go tool trace或手动加日志:在Lock()前打点,记录 goroutine ID 和时间戳,对比是否出现“进不去、出不来”的情况
真正麻烦的从来不是 panic 那一刻,而是那些没 panic 却卡在 mutex 或带缓冲 channel 上的 goroutine——它们让程序逻辑错乱,却安静得像什么都没发生。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











