go vet 能检测部分明显死锁模式,如对未初始化的nil channel操作或同一goroutine中连续发送接收;但无法覆盖多数实际死锁场景。

go vet 能发现哪些 channel 死锁
go vet 只能捕获极少数明显模式,比如对未初始化的 nil channel 执行 ch 或 <code>。它不会报任何“可能死锁”的警告,更不会识别 <code>for range ch 忘关、或 sender/receiver 启动顺序错这类逻辑问题。
真正能被 go vet 拦下的,基本只有两类:
- 声明但未
make的chan变量直接读写(编译通过,运行必卡) - 同一 goroutine 中对无缓冲
chan连续ch 和 <code>(静态可判定的自阻塞)
其他所有常见死锁——比如 receiver 没启动就发数据、range 不 close、多个 goroutine 争抢关闭权——go vet 完全沉默。别把它当死锁防火墙,它只是个基础语法守门员。
看到 all goroutines are asleep - deadlock! 就是真卡死了
这不是延迟、不是慢,是 Go 运行时确认“程序彻底走不动了”后立即 panic。触发条件非常明确:所有 goroutine 全部处于阻塞态,且没有任何一个能被唤醒(比如没人收 channel、没人放锁、没人关通道)。
关键点:
- 只要还有一个 goroutine 在 running 或 runnable 状态,就不会 panic —— 所以没报错 ≠ 没问题
- panic 输出里会列出每个 goroutine 的当前堆栈,重点看
main卡在哪一行,再扫其他 goroutine 是否全停在同一个ch 或 <code> -
GODEBUG=schedtrace=1000能快速验证:如果输出中goroutines: 1持续多秒,而你预期有多个,大概率主 goroutine 已卡死,其余早已退出
注意:pprof 在这种 panic 发生时根本用不上——runtime 已经崩溃,HTTP handler 压根没机会响应。
为什么 select + default 不是死锁解药
select 加 default 确实能避免永久阻塞,但它解决的是“不卡住”,不是“通信逻辑正确”。很多开发者误以为加了 default 就安全了,结果掩盖了真正的配对缺失。
典型陷阱:
- 本该等数据但写了
default,结果逻辑跳过关键处理,变成静默失败 select { case v, ok := —— 如果 <code>ch根本没人发,这段代码会无限轮询,消耗 CPU,还掩盖了 sender 缺失的问题- 对带缓冲 channel 错误依赖
len(ch) == 0判断是否可读——无缓冲 channel 的len永远是 0,这个判断毫无意义
真正可靠的通信契约,永远建立在明确的生命周期控制上:谁发、谁收、谁关,且只能由 sender 关闭。
最常漏掉的三个死锁高危点
线上 80% 的 channel 死锁集中在以下三处,错误极其隐蔽,且往往只在特定调度时机暴露:
-
for range ch但忘了close(ch):receiver 永远等不到 EOF,goroutine 泄漏而非 panic,日志里完全无声 -
sync.WaitGroup的Add/Done顺序错:比如wg.Add(1)放在 goroutine 启动之后,导致wg.Wait()永远等不到完成信号 - 在
init函数里起 goroutine 并操作 channel:init 是同步执行的,若 channel 操作阻塞,整个包初始化失败,程序启动即 panic,但堆栈指向 init 而非业务逻辑
这些地方不报 all goroutines are asleep,却比死锁更难排查——因为它们不 crash,只悄悄拖垮服务。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











