sync.Cond 是 Go 中需配合锁使用的低阶同步原语,必须用 for 循环检查条件以防虚假唤醒,Signal/Broadcast 需在持锁时调用,cond.L 仅为内部使用,多数场景优先选用 channel。

sync.Cond 不是语言学习技巧,它是 Go 并发编程中一个明确、低阶的同步原语,必须配合锁使用,且容易因误用导致死锁或唤醒丢失。别被“条件变量”这个术语误导——它不自动判断条件,也不管理状态,只负责“挂起”和“唤醒”。
Wait() 必须在 for 循环里调用,不是 if
虚假唤醒(spurious wakeup)真实存在:Wait() 返回时,条件可能依然不成立。Go 不保证唤醒一定由 Signal() 或 Broadcast() 触发。
- 错误写法:
if !ready { cond.Wait() }→ 一旦虚假唤醒,直接跳过检查,读到脏数据或 panic - 正确写法:
for !ready { cond.Wait() }→ 每次唤醒后重新校验条件 - 哪怕你 100% 确信不会虚假唤醒,也得写
for—— 这是 sync.Cond 的契约,不是建议
Signal() 和 Broadcast() 必须在持有锁时调用
Signal() 和 Broadcast() 本身不操作共享状态,但它们的语义依赖于“状态已更新”这一前提。如果在解锁后才发信号,等待协程可能在状态变更前就被唤醒,又掉回循环里空等。
- 错误顺序:
mu.Unlock(); cond.Signal()→ 唤醒时机错位,消费者醒来发现queue还是空的 - 正确顺序:
mu.Lock(); queue = append(queue, x); cond.Signal(); mu.Unlock() - 例外:只有当你确认唤醒逻辑与状态更新完全解耦(极少见),才考虑先 unlock 再 signal,但要加注释说明理由
Cond.L 是锁指针,不是你传进去的锁副本
sync.NewCond(&mu) 中的 &mu 是地址;cond.L 就是它。你不能混用其他锁,也不能把 cond.L 当作普通 *sync.Mutex 随意 Lock/Unlock —— 它专供 Wait() 内部使用。
- 错误用法:
cond.L.Lock(); defer cond.L.Unlock()→ 可能和Wait()内部锁机制冲突 - 正确用法:始终用你初始化时传入的那个锁变量(如
mu.Lock()),cond.L只用于cond.Wait()自动释放/重获取 - 如果你用了
sync.RWMutex,cond.L就是*sync.RWMutex,此时Wait()会调用RUnlock()和RLock(),注意读写锁语义
sync.Cond?大多数时候,channel 更安全、更直观;只有当你需要细粒度控制唤醒数量(比如只唤醒一个空闲 worker)、或状态变更不伴随数据传递(比如“配置已热加载完成”这种纯信号)时,sync.Cond 才值得引入。


















