sync.Cond.Wait() 必须在已加锁状态下调用,否则 panic;必须用 for 循环检查条件以防虚假唤醒;Signal/Broadcast 选择取决于状态变更粒度;Cond 不传递数据,仅通知共享状态变化。

sync.Cond.Wait() 前没加锁就 panic
调用 c.Wait() 时如果当前 goroutine 没持有 c.L 所指向的锁,运行时会直接 panic,错误信息是 sync: inconsistent mutex state。这不是逻辑错误,而是强制契约——Wait 必须在已加锁状态下进入。
- 常见错误:把
mu.Lock()放在for循环外面但忘了 defer 或提前 unlock;或者误以为Wait会帮你加锁 - 正确姿势:必须紧挨着
c.Wait()的上一行执行mu.Lock(),且不能在Wait前mu.Unlock() - 注意:
c.L是只读字段,不能赋值;传给sync.NewCond()的锁对象,后续所有操作都得用它,换锁或复用其他 mutex 会导致未定义行为
为什么必须用 for !condition { c.Wait() } 而不是 if
因为操作系统或调度器可能虚假唤醒(spurious wakeup)——没有被 Signal 或 Broadcast 触发,goroutine 也醒了。此时条件大概率仍不满足,若用 if 就直接往下走,读到脏数据或触发 panic。
- 典型现象:消费者从空队列取元素、等待 headers 为非 nil 却拿到 nil、读取未初始化的结构体字段
-
for循环不是可选项,是强制要求;每次唤醒后都要重新检查共享状态是否真满足 - 条件判断必须是纯内存读取,不能含副作用(比如调用函数改变状态),否则可能漏判或重复执行
Signal 和 Broadcast 选哪个?关键看状态变更粒度
Signal() 只唤醒一个等待者,Broadcast() 唤醒全部。选错会导致任务丢失或资源争抢加剧。
- 用
Signal:单个任务就绪(如生产者 push 一个 item 到有界队列,只够一个消费者取) - 用
Broadcast:全局状态重置(如关闭信号到来、缓存整体失效、headers 全部加载完成) - 坑点:在循环中反复
Signal但等待者不止一个,容易漏唤醒;反过来,在高并发下滥用Broadcast会让大量 goroutine 同时抢锁,造成 thundering herd 问题
sync.Cond 不是 channel 替代品,别硬套通信场景
它不传递数据,只通知“条件可能变了”。如果你需要传值、解耦生产/消费、或做背压控制,该用 chan 就用 chan。
立即学习“go语言免费学习笔记(深入)”;
- 适用场景很窄:多个 goroutine 等待**同一份共享状态变更**(如:文件加载完成、配置热更新生效、连接池初始化完毕)
- 反模式:用
Cond实现 worker pool 任务分发——每个任务内容不同,但Cond不带 payload,还得额外配 map 或 channel 中转 - 性能提示:
Cond本身开销极小,瓶颈永远在锁竞争和唤醒后的条件重检;别为了“看起来高级”而绕过更直观的chan或sync.Once


















