cond.Wait 和 cond.Signal 的正确使用场景是什么?
条件变量的等待操作必须置于while循环中以防虚假唤醒;条件变量的通知操作需在持锁修改共享状态后、释放锁前调用;两者须共用同一把锁。条件变量适用于低频、长时间等待共享状态变更的场景,可避免轮询开销。
Condition 变量的使用有几个关键原则:cond.Wait 必须在 while 循环中调用,因为虚假唤醒和竞态要求反复检查谓词;cond.Signal 须在持锁修改状态后、释放锁前调用;cond.Wait 与 cond.Signal 必须共用同一把锁;Condition 适用于低频、长时间等待共享状态变更的场景。下面逐一展开。
cond.Wait 必须在 while 循环中调用
直接用 if 判断后调用 cond.wait() 是最容易踩的坑之一。问题在于,操作系统和运行时环境都允许线程在没有任何信号的情况下被唤醒——这被称为虚假唤醒(spurious wakeup)。另外,即使只有一个线程被 signal 唤醒,也可能出现多个线程同时醒来的情况(多核系统下尤为常见)。如果只用 if 检查一次,唤醒后就直接往下执行,很可能条件已经不成立,数据错乱就在所难免。

正确的做法是:拿到锁之后,用 while 循环反复检查谓词(predicate),只有当条件真正满足时才跳出循环。以 Go 为例:
lock.Lock()
defer lock.Unlock()
for len(queue) == 0 { // 谓词:队列为空
cond.Wait() // 自动释放 lock,唤醒后自动重新获取
}
item := queue[0]
queue = queue[1:]
- 绝对不能写成
if len(queue) == 0 { cond.Wait() }——那相当于赌运气。 - 即便你只期望唤醒一个线程,也要循环检查,因为唤醒不等于条件成立。
- 这条规则适用于所有主流实现:Ja va 的
await()、Go 的Cond.Wait()、C 的pthread_cond_wait(),无一例外。
cond.Signal 应在修改共享状态后、释放锁前调用
cond.Signal() 本身不做数据操作,也不释放锁,它的作用只是向等待队列发一个“醒醒”通知。如果先释放锁再调用 signal,就会引入一个极难排查的竞态:新线程可能在你释放锁之后、signal 之前抢先加锁并再次阻塞,等你的 signal 发出时,已经没有等待者了——通知丢失,等待线程永远醒不来。
安全的顺序是:
lock.Lock()
// 修改共享状态:如 queue.push(item), count++
if len(queue) == 1 { // 刚从空变为非空,值得通知消费者
cond.Signal()
}
lock.Unlock()
- 必须在持有锁期间判断是否需要 signal —— 这样才能原子地观察状态变化并决定通知。
- 不要在 unlock 之后才调用
cond.Signal(),哪怕你觉得“逻辑上更顺”。 - 如果需要唤醒所有等待者(例如关闭信号、批量就绪),请用
cond.Broadcast()或cond.SignalAll(),但要注意性能开销更高。
cond.Wait 和 cond.Signal 必须共用同一把锁
Condition 不是一个独立的同步原语,它必须与且仅与一个互斥锁绑定。Go 的 sync.Cond{L: &mutex}、Ja va 的 ReentrantLock.newCondition()、C 的 pthread_cond_wait(&cond, &mutex) 都强制要求传入 mutex。跨锁使用会引发未定义行为——轻则死锁,重则内存破坏。
常见的误用案例:
- 用
mutexA加锁,却传mutexB给cond.Wait()→ 直接崩溃或静默失败。 - 一个
cond被多个不同锁的临界区复用 → 状态混乱,唤醒完全失效。 - 忘记
cond.Wait()返回后已经自动重新加锁,手动多加一次导致死锁。
何时该用 cond 而不是 channel 或 mutex + sleep?
Condition 最适合“等待某个共享内存状态改变”的场景,尤其是当状态由多个线程协同维护、通知频率低、等待时间长的时候。相比轮询 + time.Sleep(),它能省下大量 CPU;相比 channel,它更贴近底层共享变量模型(比如 ring buffer、引用计数、文件描述符就绪等)。
但也有些铁律需要记住:
- channel 在 Go 生态中更适合 goroutine 间传递数据,语义清晰,自带缓冲和关闭机制。
- 如果只是单次通知(比如初始化完成),用
sync.Once或sync.WaitGroup更轻量。 - Condition 无法跨进程使用;如果需要进程间通信,请考虑 POSIX 信号量或消息队列。
最后一点最容易忽略:Condition 的等待队列不保证 FIFO 顺序,signal 唤醒哪个线程完全取决于调度器和底层实现。永远不要依赖唤醒顺序来做业务逻辑判断——那不是它承诺的功能。


































