sync.Cond.Wait 返回时条件未必成立,因唤醒可能早于等待、存在虚假唤醒或竞态,故需配合互斥锁与循环检查谓词,确保逻辑正确性。
使用 sync.cond 时为何必须在循环中调用 wait?`sync.cond.wait` 返回时条件未必成立,因唤醒可能早于等待、存在虚假唤醒或竞态,故需配合互斥锁与循环检查谓词,确保逻辑正确性。
sync.Cond 是 Go 中用于协程间条件同步的重要原语,但它本身不保存状态——它既不记录“是否已被广播”,也不保证“唤醒即条件满足”。这与信号量(semaphore)或通道(channel)有本质区别:Broadcast() 仅唤醒当前正在 Wait() 的 goroutine;若无 goroutine 阻塞,该信号将彻底丢失;若 goroutine 在 Broadcast() 之后才进入 Wait(),它将永远阻塞。
因此,官方文档明确要求:必须在 for !condition { c.Wait() } 循环中使用 Wait。原因有三:
- 虚假唤醒(Spurious Wakeup):底层操作系统(如 futex)可能在无显式 Signal/Broadcast 时唤醒线程,Go 的 runtime 为兼容性保留了这一行为;
- 唤醒时机错位(Race on Broadcast Timing):Broadcast() 可能在某个 goroutine 还未执行到 Wait() 前就已完成,导致该 goroutine 后续调用 Wait() 时永远阻塞;
- 条件状态非原子性:条件本身(如 done == true)是用户维护的共享变量,其修改与 Broadcast() 并非原子操作,必须由同一把锁保护,并在每次 Wait() 返回后重新验证。
以下是一个修复后的典型示例,展示了符合规范的用法:
AI 舌诊面诊问诊医疗系统 + 诊疗商城系统源码是一套结合 AI 辅助诊断与商品转化的健康类程序,基于浩创云、ThinkPHP(PHP)开发,支持微信服务号与 H5 端,适合做中医舌诊面诊问答、健康建议推送以及配套诊疗商品的运营。
type processor struct {
n int
c *sync.Cond
started chan int
done bool // 谓词:表示所有前置任务完成,可继续执行
}
func newProcessor(n int) *processor {
mu := &sync.Mutex{}
return &processor{
n: n,
c: sync.NewCond(mu),
started: make(chan int, n),
done: false,
}
}
func (p *processor) start() {
for i := 0; i <p>⚠️ 注意事项:</p>
- condition() 必须在 c.L.Lock() 持有锁的前提下读取,否则存在数据竞争;
- Wait() 内部会自动解锁 c.L,并在被唤醒且重新获得锁后返回,因此切勿在 Wait() 前手动解锁;
- 若只需唤醒一个等待者,优先使用 Signal();若需唤醒全部,用 Broadcast();但无论哪种,都必须配合循环谓词检查;
- 避免依赖外部同步机制(如本例中原有的 started channel)替代条件谓词——它仅解决部分时序问题,无法覆盖虚假唤醒和逻辑状态一致性。
总结:sync.Cond 不是“事件通知器”,而是“协作式条件等待工具”。它的正确用法范式只有一个:锁 + 谓词 + 循环 Wait。忽略此模式可能导致隐蔽的死锁、逻辑跳过或竞态崩溃,尤其在高并发或复杂调度场景下。始终让条件判断成为临界区的一部分,才能写出健壮的并发代码。










