商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > cond.Wait 和 cond.Signal 的正确使用场景是什么?

cond.Wait 和 cond.Signal 的正确使用场景是什么?

  发布于2026-07-12 阅读(0)

扫一扫,手机访问

Condition 变量的使用有几个关键原则:cond.Wait 必须在 while 循环中调用,因为虚假唤醒和竞态要求反复检查谓词;cond.Signal 须在持锁修改状态后、释放锁前调用;cond.Waitcond.Signal 必须共用同一把锁;Condition 适用于低频、长时间等待共享状态变更的场景。下面逐一展开。

cond.Wait 必须在 while 循环中调用

直接用 if 判断后调用 cond.wait() 是最容易踩的坑之一。问题在于,操作系统和运行时环境都允许线程在没有任何信号的情况下被唤醒——这被称为虚假唤醒(spurious wakeup)。另外,即使只有一个线程被 signal 唤醒,也可能出现多个线程同时醒来的情况(多核系统下尤为常见)。如果只用 if 检查一次,唤醒后就直接往下执行,很可能条件已经不成立,数据错乱就在所难免。

cond.Wait 和 cond.Signal 的正确使用场景是什么?

正确的做法是:拿到锁之后,用 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 加锁,却传 mutexBcond.Wait() → 直接崩溃或静默失败。
  • 一个 cond 被多个不同锁的临界区复用 → 状态混乱,唤醒完全失效。
  • 忘记 cond.Wait() 返回后已经自动重新加锁,手动多加一次导致死锁。

何时该用 cond 而不是 channel 或 mutex + sleep?

Condition 最适合“等待某个共享内存状态改变”的场景,尤其是当状态由多个线程协同维护、通知频率低、等待时间长的时候。相比轮询 + time.Sleep(),它能省下大量 CPU;相比 channel,它更贴近底层共享变量模型(比如 ring buffer、引用计数、文件描述符就绪等)。

但也有些铁律需要记住:

  • channel 在 Go 生态中更适合 goroutine 间传递数据,语义清晰,自带缓冲和关闭机制。
  • 如果只是单次通知(比如初始化完成),用 sync.Oncesync.WaitGroup 更轻量。
  • Condition 无法跨进程使用;如果需要进程间通信,请考虑 POSIX 信号量或消息队列。

最后一点最容易忽略:Condition 的等待队列不保证 FIFO 顺序,signal 唤醒哪个线程完全取决于调度器和底层实现。永远不要依赖唤醒顺序来做业务逻辑判断——那不是它承诺的功能。

本文转载于:https://www.php.cn/faq/2376054.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注