sync.Cond 必须搭配 sync.Mutex 或 sync.RWMutex 使用,需用 sync.NewCond(&mu) 初始化;Wait() 前必须加锁且在 for 循环中检查条件;Signal/Broadcast 必须在锁内调用,且不缓存信号。

sync.Cond 的核心使用前提:必须搭配 sync.Mutex 或 sync.RWMutex
直接 new(sync.Cond) 是无效的,sync.Cond 本身不维护锁状态,它只负责“等待”和“唤醒”,所有对共享变量的读写都必须在持有锁的前提下进行。漏掉这一步,轻则数据竞争,重则 panic: sync: inconsistent cond。
正确做法是用 sync.NewCond 包装一个已初始化的互斥锁:
var mu sync.Mutex cond := sync.NewCond(&mu)
- 不能传入未取地址的锁(如
sync.NewCond(mu)),会编译失败 - 不能复用同一个
sync.Cond实例管理多个不同锁保护的条件,否则唤醒逻辑错乱 -
sync.RWMutex也可用,但注意cond.L必须指向其&sync.RWMutex,且调用cond.Wait()前必须持有写锁(因为Wait内部会先解锁再阻塞)
Wait() 调用前必须先加锁,且需在循环中检查条件
cond.Wait() 会自动释放关联锁、挂起 goroutine,并在被唤醒后重新获取锁 —— 但它**不保证唤醒时条件一定成立**。常见错误是写成单次 if 判断后就 Wait,导致虚假唤醒(spurious wakeup)或错过信号。
标准写法必须是 for 循环 + 条件判断:
立即学习“go语言免费学习笔记(深入)”;
mu.Lock()
for !conditionMet() {
cond.Wait()
}
// 此时 conditionMet() 为 true,且 mu 已锁定
mu.Unlock()
-
conditionMet()必须是访问受该锁保护的共享变量的逻辑,比如len(queue) > 0 - 不能在
Wait()后直接操作共享数据,必须再次确认条件成立 - 如果条件依赖多个变量,确保它们都在同一把锁下保护,否则仍可能产生竞态
Broadcast() 和 Signal() 的行为差异与适用场景
cond.Signal() 最多唤醒一个正在 Wait 的 goroutine;cond.Broadcast() 唤醒所有 Wait 中的 goroutine。选哪个不是看“要不要快”,而是看语义是否匹配:
- 生产者放入一个任务 → 消费者只需一个被唤醒 → 用
Signal() - 生产者清空缓冲区 / 关闭资源 / 设置全局终止标志 → 所有等待者都需要响应 → 用
Broadcast() - 误用
Signal()可能导致部分 goroutine 永久阻塞(尤其在条件非独占时,比如多个消费者等“任意一个有数据”) -
Broadcast()开销略大,但避免了唤醒遗漏;Go 运行时对此做了优化,实际性能差距通常可忽略
唤醒时机必须在锁内完成,且不能依赖唤醒顺序
唤醒操作(Signal/Broadcast)**应在持有锁期间调用**,否则可能唤醒尚未进入 Wait 的 goroutine,造成信号丢失。典型反例:
// ❌ 错误:先解锁再唤醒 mu.Unlock() cond.Signal() // 此时可能没有 goroutine 在 Wait,信号丢失
正确写法:
mu.Lock() // 修改共享状态,例如 queue = append(queue, item) cond.Signal() // 或 Broadcast() mu.Unlock()
- 即使你刚改完状态并调用了
Signal,也不能假设被唤醒的 goroutine 立刻执行——调度仍是异步的 - Go 不保证
Signal唤醒的是“最先 Wait”的 goroutine,也不提供优先级控制 - 若需严格 FIFO 行为,
sync.Cond无法满足,得用 channel + 队列或额外序号机制
真正容易被忽略的点在于:条件变量不是事件队列,它不缓存信号。一次 Signal 只对应一次唤醒机会,没接收就丢,而且必须靠程序员用锁+循环来兜底。


















