cond.Wait() 卡住的根本原因是未在调用前持有与之关联的 *sync.Mutex 锁,或使用了不支持的 sync.RWMutex;必须先 mu.Lock() 再 cond.Wait(),唤醒后需重新获取锁才返回。

cond.Wait() 为什么总是卡住不返回
根本原因不是 cond.Wait() 本身有问题,而是它必须和互斥锁配合使用——而且必须在调用前已持有锁,调用后自动释放;唤醒后重新获取锁才返回。如果忘了加锁,或锁对象和 cond 不是同一个 *sync.Mutex(比如用了 sync.RWMutex),就会永久阻塞。
常见错误写法:
var mu sync.Mutex var cond = sync.NewCond(&mu) // ❌ 错误:没加锁就 Wait cond.Wait() // 永远卡住 // ✅ 正确:先 Lock,Wait 内部会 Unlock,唤醒后重新 Lock 才返回 mu.Lock() cond.Wait() mu.Unlock()
-
cond.Wait()必须在已锁定的mu下调用,否则 panic 或死锁 - 唤醒后线程不会立刻执行,要等重新抢到
mu锁才从Wait()返回 - 不要用
sync.RWMutex初始化sync.Cond,Go 标准库不支持
cond.Signal() 和 cond.Broadcast() 的区别在哪
两者都用于通知等待者,但唤醒范围不同:cond.Signal() 最多唤醒一个正在 Wait() 的 goroutine;cond.Broadcast() 唤醒所有。
关键点在于「唤醒不等于立即执行」——被唤醒的 goroutine 还得排队抢锁,谁先抢到谁先走。所以即使只调 Signal(),也不能保证是“最早等待的那个”被选中,调度是公平但非 FIFO 的。
- 用
Signal()适合“一个任务完成,只需通知一个消费者”的场景(如工作池取一个任务) - 用
Broadcast()适合状态变更影响所有等待者(如关闭信号、资源重置) - 如果等待者数量不确定,又只希望唤醒一个,别依赖
Signal()的“精确性”,要在业务逻辑里加条件检查(见下一条)
为什么唤醒后还要检查条件变量的真值
Go 的 cond.Wait() 是虚假唤醒(spurious wakeup)友好的设计:它可能在没被显式通知的情况下返回。更常见的是,多个 goroutine 同时被 Broadcast() 唤醒,但只有第一个能真正处理成功,其余必须重新判断条件是否满足,否则会出错。
标准写法永远是:
mu.Lock()
for !conditionMet() { // 必须用 for,不能用 if
cond.Wait()
}
// 此时 conditionMet() 为 true,安全执行后续逻辑
mu.Unlock()
- 用
if替代for是最常见 bug 来源,会导致逻辑跳过或重复处理 - 条件判断函数
conditionMet()必须访问受同一把锁保护的共享状态 - 唤醒只是提示“可能有变化”,不是“已经满足”,这点和 pthread_cond_wait 一致
cond 在 channel 配合下还有没有存在必要
纯通知场景下,chan struct{} 往往更简单、更 Go-idiomatic;但 sync.Cond 不可替代的场景是:需要等待某个**共享内存状态**变化,且该状态无法通过 channel 精确建模(比如等待 map 中某个 key 出现、等待 slice 长度超过阈值、或与外部 C 库共享内存)。
- channel 适合“事件驱动”或“生产者-消费者”模型,语义清晰、无锁、自带同步
-
sync.Cond适合“状态驱动”:你关心的不是“有没有通知”,而是“当前状态是否符合预期” - 混用风险高:不要在
Wait()期间向 channel 发送,也不要在Signal()后 close channel,容易引发竞态
真实项目里,sync.Cond 出现场景越来越少,但一旦遇到“等待某段受锁保护的状态成立”,它仍是唯一干净的方案。别为了少写几行 channel 就硬套 Cond,也别因为不熟就绕开它去搞轮询或 sleep。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











