go运行时检测到所有goroutine休眠且无唤醒路径时立即panic退出;checkdead()在调度末尾扫描并终止进程;无缓冲channel单goroutine发送必然死锁。

fatal error: all goroutines are asleep - deadlock! 一出现就停机,没商量
Go 运行时不是“检测到可能死锁”,而是直接判定程序已丧失全部活性,立刻 panic 并退出。checkdead() 函数在每次调度循环末尾扫描:所有 goroutine 都处于 park 状态(等锁、等 channel、等 wg.Wait()),且没有任何唤醒路径——这时它就终止进程。defer 不执行,日志不刷全,堆栈只留最后一刻快照。
无缓冲 channel 单 goroutine 发送必死锁
这是最常被忽略的“语法级”死锁。无缓冲 channel 的发送操作 c 必须有另一个 goroutine 同时执行 <code>,否则永远阻塞。
- main 中写
c := make(chan int); c → 立即 panic -
for v := range ch要求ch被关闭,否则无限等待;若发送方 goroutine 退出前没调close(ch),接收方就卡死 - 用
select { case 代替裸读,但若漏掉 <code>default或time.After,仍会阻塞 - 带缓冲 channel 容量为 0(
make(chan int, 0))等价于无缓冲,别被名字骗
多个 sync.Mutex 锁顺序不一致直接触发循环等待
两个 goroutine 分别按不同顺序获取同一组锁,就是 Coffman 死锁的经典闭环。Go 不检查锁依赖图,只看最终状态——只要结果是 A 在等 B、B 在等 A,fatal error 就来了。
在 Go 中使用 google/wire 实现编译时依赖注入——wire.NewSet、wire.Build、wire.Bind(接口→实现)、wire.Struct、wire.Value、wire.Interface
- 错误模式:
go func() { muA.Lock(); muB.Lock() }()和go func() { muB.Lock(); muA.Lock() }() - 修复不是靠运气或加 sleep,而是硬性约定顺序:按变量声明顺序、字段名 ASCII 序、或业务层级(如
userMu→orderMu→paymentMu) - 把多锁逻辑封装成函数,内部统一顺序,外部只暴露一个入口,避免调用方自行组合
-
sync.RWMutex混用时更危险:一个 goroutineRLock()后再Lock(),会自己卡自己
goroutine 泄漏 + channel 阻塞 = 隐性死锁
不报 fatal error,但效果一样:CPU 归零、内存缓涨、请求无响应。本质是 goroutine 在 channel 上永久等待,而发送方或接收方早已退出或 never 启动。
- 常见于:启动 goroutine 发送数据,但忘了 close(ch);或接收方用
for range ch,发送方却用select带 default 轮询,从不 close - 用
runtime.NumGoroutine()在测试中打点,上线后突增说明泄漏已发生 - pprof 查
/debug/pprof/goroutine?debug=2,重点关注状态为chan receive或semacquire的 goroutine 堆栈 - 真正安全的做法不是加 defer close(ch),而是让发送方明确知道接收方是否还活着——用
context.WithCancel或带超时的select
死锁不是并发的副作用,而是通信契约破裂的即时判决。channel 要配对,锁要排序,goroutine 生命周期要可追踪——少一个环节,运行时就给你盖章“deadlock”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










