Go 协程出现 deadlock detected 报错时,最直接的处理方式是查看运行时打印的堆栈跟踪信息,定位所有处于阻塞状态的协程代码行。修复重点在于调整 channel 收发逻辑或互斥锁解锁顺序,确保至少有一个协程能继续执行。
先说结论:该报错意味着所有协程都处于阻塞等待状态,必须通过代码逻辑调整打破等待循环。
- 先确认:读取 panic 输出的堆栈跟踪,找到处于 [chan send]/[chan receive]/[sync.Mutex.Lock] 状态的协程。
- 先处理:为 channel 增加缓冲区、使用 select 设置超时或缺省分支、检查 mutex 是否缺少 defer unlock。
- 再验证:重新运行程序,确认不再触发 fatal error 且业务逻辑符合预期。
快速处理思路
由于死锁是逻辑错误,没有单一命令能自动修复,建议按以下思路操作:
- 保留报错现场的堆栈信息,不要直接重启掩盖问题。
- 对比阻塞协程的调用栈,寻找循环依赖或资源等待链。
- 修改代码后重新编译运行,观察是否复现。
为什么会这样
Go 运行时检测到所有协程都无法继续执行时触发该报错。当主协程或其他协程在等待 channel 通信或锁释放,而没有其他协程能提供数据或释放锁时,运行时判定为死锁并终止程序。
分步处理
1. 分析堆栈:查看报错下方的 goroutine 列表,标记状态为 sleep 的协程,适用场景为本地开发或测试环境复现。
2. 定位代码:找到堆栈中对应的文件行号,确认是 channel 操作还是锁操作,操作动作为编辑源代码。
3. 修改逻辑:如果是 channel,考虑改为 buffered channel 或添加 select default;如果是锁,确保成对使用 Lock/Unlock 且避免嵌套死锁,风险边界为可能改变原有同步语义。
4. 回归测试:覆盖并发场景,确保修改未引入新竞争,验证结果为程序稳定运行。
怎么验证是否生效
运行 go run 或执行编译后的二进制文件,观察程序是否正常退出或继续运行,不再输出 fatal error: all goroutines are asleep - deadlock!。同时检查业务日志确认数据流转正常。
常见坑
- 无缓冲 channel 同步发送接收:发送方和接收方在同一协程或相互等待,导致双方都无法继续。
- 忘记解锁:获取锁后 panic 导致未执行 unlock,建议使用 defer mu.Unlock() 确保释放。
- 全局变量竞争:多个函数间接依赖同一全局锁导致循环等待,需谨慎设计锁粒度。
常见问题
死锁和协程泄漏有什么区别?
死锁是所有协程卡住导致程序崩溃,协程泄漏是协程一直运行但不退出消耗资源。
增加 channel 缓冲区一定能解决死锁吗?
不一定,缓冲区只能缓解同步等待,逻辑循环依赖仍需代码调整。
参考来源
- Go Official Documentation - The Go Programming Language, https://go.dev/doc/