<p>死锁错误表现为“fatal error: all goroutines are asleep - deadlock!”,所有goroutine因channel收发、锁未释放或WaitGroup不匹配等原因永久阻塞,程序直接终止;需用pprof/goroutine定位卡点,-race无法检测死锁。</p>

死锁错误长什么样?fatal error: all goroutines are asleep - deadlock!
这是 Go 程序遇到死锁时最典型的报错,说明所有 goroutine 都在等某个条件(比如 channel 接收、互斥锁、WaitGroup 等),没人能继续推进。它不是 panic,不带堆栈,也不触发 defer,一旦发生就直接终止——所以必须在运行时暴露,不能靠测试覆盖率“蒙混过关”。
常见诱因包括:
• 向无缓冲 chan 发送数据,但没有 goroutine 在另一端接收
• 使用 sync.Mutex 时忘记 Unlock(),或重复 Lock()
• sync.WaitGroup 的 Add() 和 Done() 数量不匹配,导致 Wait() 永远阻塞
• 在同一个 goroutine 中对同一 chan 同时读写(尤其是带缓冲但容量为 0 的情况)
怎么快速定位是哪条 channel 或哪个锁卡住了?
Go 自带的 runtime/pprof 是最轻量、最可靠的手段。只要程序还在阻塞(没崩溃前),就能导出 goroutine 栈信息,看到每个 goroutine 卡在哪一行、等什么资源。
实操建议:
• 在启动时加一段代码:
go func() {
http.ListenAndServe("localhost:6060", nil)
}(),然后用 curl http://localhost:6060/debug/pprof/goroutine?debug=2 查看全部 goroutine 的完整调用栈• 关键看状态为
chan receive、semacquire(锁)、sync.runtime_SemacquireMutex 的行,它们对应 channel 接收、Mutex 等待、WaitGroup 阻塞• 如果程序已崩溃,就只能靠复现 + 加日志:在 channel 操作前后打点,例如
log.Printf("before send to %p", &ch),配合 go tool trace 分析执行流
go run -race 能发现死锁吗?
不能。-race 检测的是**竞态条件(data race)**,即多个 goroutine 无同步地读写同一变量;而死锁是**逻辑阻塞**,和内存访问无关。两者常被混淆,但检测机制完全不同。
立即学习“go语言免费学习笔记(深入)”;
所以:
• 怀疑有竞态 → 用 go run -race
• 怀疑有死锁 → 用 pprof/goroutine 或重构逻辑加超时
• 不要用 select { default: } 来“绕过” channel 阻塞,这容易掩盖问题,且可能引入新 bug(比如漏处理关键消息)
预防死锁的三个硬约束
死锁本质是循环等待,Go 并发模型里最容易形成环的地方就是 channel 和锁的嵌套使用。守住以下三点能避开 90% 的坑:
• 所有 chan 操作必须有明确的 sender/receiver 角色划分,避免单 goroutine 自己往自己读的 channel 发送(除非带缓冲且容量 > 0)
• sync.Mutex 和 sync.RWMutex 必须成对出现:Lock()/Unlock() 或 Rlock()/RUnlock(),且不能跨函数边界传递锁状态(比如把未解锁的 mutex 传进另一个函数)
• sync.WaitGroup 的 Add() 必须在 go 语句之前调用,绝不能在 goroutine 内部调用——否则 Wait() 可能永远等不到 Done()
复杂点在于,这些约束在大型模块间协作时容易被忽略:比如 A 包封装了一个带 channel 的服务,B 包调用它却忘了启动接收 goroutine。这种耦合性问题,光靠工具发现不了,得靠接口契约和文档约束。










