Go 程序出现 fatal error: all goroutines are asleep - deadlock! 意味着运行时检测到所有 goroutine 都在等待且无法被唤醒,调试核心是分析崩溃时的 stack trace 定位阻塞点。该错误仅发生在所有 goroutine 均阻塞时,单个 goroutine 阻塞不会触发此 fatal。
先说结论:这是一个运行时致命错误,必须通过堆栈信息定位相互等待的资源,无法通过重启自动恢复。
- 先确认:检查崩溃日志中的 goroutine 堆栈状态
- 先处理:识别 channel 发送/接收或 mutex 锁的循环等待
- 再验证:使用
-race参数或 pprof 复现竞争条件
命令速用版
调试此类问题最直接的方式是获取崩溃堆栈并在本地复现,必要时开启竞态检测。
# 本地运行并开启竞态检测,捕捉潜在的数据竞争
go run -race main.go
# 如果程序已崩溃,查看标准错误输出中的 stack trace
# 重点查找状态为 [sleep] 或 [chan send]/[chan receive] 的 goroutine为什么会这样
Go 运行时调度器会定期检查是否存在可执行的 goroutine,当发现所有 goroutine 都处于阻塞状态且没有外部事件能唤醒它们时,会触发 deadlock 保护。
这通常不是因为代码写死了“死锁”关键字,而是因为逻辑上形成了闭环等待。例如 goroutine A 等待 channel 1,goroutine B 持有 channel 1 却在等待 channel 2,而 channel 2 又依赖 A 释放资源。运行时无法推断业务逻辑,只能检测到“无人可运行”的状态。
分步处理
处理流程分为获取现场、分析阻塞链、修改代码三个步骤,每一步都需要确认当前状态。
第一步:获取完整堆栈
程序崩溃时控制台会输出所有 goroutine 的堆栈信息,务必保存完整日志。如果日志被截断,调整终端缓冲区大小或重定向输出到文件。
第二步:分析阻塞状态
在堆栈信息中查找状态标记,常见的阻塞状态包括 chan send、chan receive、semacquire( mutex 锁)。找到处于这些状态且长时间未返回的 goroutine 编号。
第三步:定位代码行
根据堆栈顶部的文件路径和行号,定位到具体的代码位置。检查该位置涉及的共享变量、channel 或锁是否缺乏超时机制或取消信号。
怎么验证是否生效
验证修复是否生效需要在高并发场景下稳定运行,并确认不再出现 fatal error。
使用压力测试工具对接口进行并发请求,同时观察程序日志。如果运行长时间后不再出现 deadlock 报错,且 pprof 中 goroutine 数量维持稳定,可视为修复成功。
常见坑
调试过程中容易忽略缓冲 channel 的特性以及 context 取消传播的完整性。
- 缓冲 channel 未满时发送不会阻塞,满时发送会阻塞,需确认缓冲区大小是否合理。
- 使用 sync.WaitGroup 时,确保 Add 在 Start 之前调用,且每个 Goroutine 结束都调用 Done。
- 忘记在 select 语句中添加 default 或超时 case,导致永久阻塞。
常见问题
生产环境出现 deadlock 怎么办?
生产环境无法直接调试时,应保留崩溃现场日志并重启服务恢复业务。
通过日志中的 stack trace 定位问题代码,在测试环境复现后修复。不要试图在生产环境直接 attach 调试器,这可能加重负载导致雪崩。
开启 -race 会影响性能吗?
开启 -race 参数会增加内存占用和运行时间,不建议在生产环境长期开启。
该参数主要用于测试环境捕捉数据竞争,生产环境建议使用 pprof 定期采样监控 goroutine 状态。