cond适用于多个goroutine等待同一条件并需主动唤醒的协作场景,如生产者-消费者模型;必须与sync.mutex配合使用,wait前需加锁,返回后须在锁保护下重检条件以防虚假唤醒。

Cond 适合什么场景:多个 goroutine 等待同一条件,且需主动唤醒
它不是 sync.Mutex 或 sync.WaitGroup 的替代品,而是解决“等某个状态就绪再继续”的协作问题。典型如生产者-消费者模型里,消费者等缓冲区非空,生产者写入后通知所有等待的消费者。
常见错误是把它当信号量用——比如只唤醒一个 goroutine 却没配好条件检查逻辑,结果唤醒了但条件仍不满足,直接 panic 或死锁。
- 必须和
sync.Mutex配合使用:所有对共享状态的读写、c.Wait()前都得加锁 -
c.Signal()只唤醒一个等待者,c.Broadcast()唤醒全部;选哪个取决于业务语义——比如“有新任务”适合 Broadcast,“取走一个任务”后只需 Signal 一个即可 - Wait 返回前会自动重新获取锁,所以退出 Wait 后要再次检查条件是否真满足(防止虚假唤醒)
为什么不能跳过条件重检:虚假唤醒真实存在
Go 运行时或底层 OS 可能无理由唤醒等待中的 goroutine,这不是 bug,是 POSIX 条件变量的设计约定。跳过重检会导致读到脏数据或执行非法操作。
比如消费者在 Wait 返回后直接从空切片取元素,就会 panic:panic: runtime error: index out of range。
立即学习“go语言免费学习笔记(深入)”;
- 正确模式是:
for len(queue) == 0 { c.Wait() },而不是if len(queue) == 0 { c.Wait() } - 重检必须在锁保护下进行,否则检查和后续操作之间可能被其他 goroutine 修改状态
- 条件变量本身不保存状态,它只是个通知通道——状态还得你自己管
Cond.Wait() 必须在 Lock/Unlock 之间调用,否则 panic
这是最常踩的坑:c.Wait() 内部会先释放锁,等唤醒后再抢锁。如果调用前没加锁,运行时直接 panic:sync: Cond.Wait outside of lock。
示例错误写法:
c := sync.NewCond(&sync.Mutex{})
go func() {
c.Wait() // panic!没 Lock 就 Wait
}()
- 正确顺序:Lock → 检查条件 → Wait(自动 Unlock)→ 被唤醒 → 自动 Lock → 重检 → Unlock
- 别在 defer 里 Unlock 错误位置:Wait 前必须已 Lock,Wait 返回后锁已被重新获取,此时再 Unlock 才合理
- 如果用的是
sync.RWMutex,注意Cond只接受*sync.Mutex,不支持读写锁
性能与可维护性提醒:Cond 不是首选同步原语
绝大多数场景,channel 更直观、更安全。Cond 的优势仅在于:需要广播通知 + 多个 goroutine 共享同一状态 + 对唤醒时机有精细控制(比如只唤醒特定类型等待者)。
滥用 Cond 容易让逻辑散落在各处:状态判断、修改、唤醒混在不同函数里,调试困难。
- 优先考虑 channel:带缓冲的 channel 天然适配生产者-消费者,
select支持超时和默认分支,不易出错 - Cond 更适合底层封装,比如实现自定义的资源池、事件总线、或者兼容 C-style 条件变量接口的库
- 一旦用了 Cond,就把“状态变量 + Mutex + Cond”三者绑定为一个内聚单元,别拆开传参或全局暴露
Cond 的复杂度不在 API,而在状态一致性维护——稍不注意,唤醒和条件就脱节了。










