死锁时Go运行时立即panic并打印fatal error,需依据panic日志中的goroutine堆栈分析:重点关注main goroutine停顿位置、阻塞的channel操作及等待的锁状态。

Go 程序一旦死锁,运行时会立即 panic 并打印 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,每秒输出调度快照,观察 g 数量是否长期为 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 空转——高频轮询建议加 time.Sleep(time.Millisecond) 或 runtime.Gosched()。
排查 sync.Mutex 和 sync.WaitGroup 类死锁
这类死锁不触发 runtime panic,但会让 goroutine 卡在 sync.(*Mutex).Lock 或 sync.(*WaitGroup).Wait,得靠 pprof 主动抓。
- 同一个 goroutine 连续调用两次
mu.Lock()(没Unlock())→ 直接卡死 -
defer mu.Unlock()写在if分支里,或提前return漏掉 → 锁永远不释放 - goroutine A 先锁
mu1再锁mu2,B 反过来先mu2后mu1→ 经典循环等待 -
WaitGroup.Add()和Done()数量不匹配 →Wait()永远阻塞
持有锁期间调用可能阻塞的操作(如写 channel、HTTP 请求)会拉长锁时间,增加冲突概率。建议:给 mutex 字段加注释说明保护哪些变量;复杂逻辑优先用 channel 通信代替共享内存加锁。
真正难排查的不是 panic 那一刻,而是那些没 panic 却长期阻塞的 goroutine——它们不会报错,但会悄悄吃掉协程数、拖慢响应、甚至掩盖真实瓶颈。别依赖“好像没挂”,要用 go tool trace 或 pprof 定期看 goroutine 状态。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










