sync.cond 不能替代 channel 或 mutex,它必须依附于 sync.mutex/sync.rwmutex,仅用于等待和通知同一条件变化,需手动加锁、循环检查条件,并正确管理生命周期。

Cond 是什么,它真能替代 channel 或 mutex 吗
sync.Cond 不是独立的同步原语,它必须依附于一个 *sync.Mutex 或 *sync.RWMutex。它本身不提供互斥,只负责“等待条件成立”和“唤醒等待者”。如果你直接用 Cond 而没配对使用锁,大概率会遇到 panic: sync: Cond.Wait with uninitialized mutex 或死锁。
- 适用场景很窄:多个 goroutine 等待**同一条件变化**(比如缓冲区非空、任务队列有新任务),且该条件由另一个 goroutine 主动通知
- 不能替代
channel:它不传递数据,也不保证唤醒顺序(FIFO 不保证) - 不能替代
mutex:Cond.L必须是你自己传入的有效锁指针,且每次Wait前必须已加锁,Wait内部会自动解锁并阻塞,返回时重新加锁
Wait 之前为什么必须先加锁,且检查条件是否成立
这是最常踩的坑:Cond.Wait 不是“等条件变成 true”,而是“在条件不成立时挂起”。它不会帮你重试判断,也不会自动循环检查。你得自己写 for 循环 + 条件判断,否则可能错过信号或虚假唤醒。
- 典型错误写法:
if !condition { cond.Wait() }—— 一旦发生虚假唤醒(spurious wakeup),就直接往下走了,但 condition 仍为 false - 正确模式必须是:
for !condition { cond.Wait() } - 条件检查和
Wait必须在同一个锁保护下,否则检查完 condition 就被别的 goroutine 改了,Wait就等了个寂寞
for len(queue) == 0 {
cond.Wait() // 进入前 mutex 已 lock,返回时已 re-lock
}
// 此时 queue 非空,安全消费
item := queue[0]
queue = queue[1:]
Signal 和 Broadcast 的实际行为差异
Cond.Signal() 只唤醒**一个**正在等待的 goroutine(具体哪个不确定),Cond.Broadcast() 唤醒**所有**。但注意:唤醒不等于立即执行——被唤醒的 goroutine 仍要竞争锁,拿到锁后才从 Wait 返回。
- 用
Signal的前提:你确定只有一个 goroutine 需要响应这次状态变化(比如生产者往满缓冲区放了一个 item,只需唤醒一个消费者) - 用
Broadcast的常见场景:状态变化影响所有等待者(比如关闭标志置位、超时清理) - 别迷信 “Broadcast 性能差”:在几十个 goroutine 级别,差异几乎不可测;真正慢的是锁竞争本身,不是唤醒逻辑
- 切记:Signal/Broadcast 调用时,
Cond.L锁可以是加着的,也可以是没加的——但通常建议在持有锁时调用,避免 notify 和 wait 之间出现竞态窗口
为什么你的 Cond 没唤醒,或者 panic 了
绝大多数问题出在生命周期管理上:sync.Cond 不是线程安全的初始化对象,它的 L 字段必须指向一个长期有效的锁,且该锁不能被回收或重复初始化。
立即学习“go语言免费学习笔记(深入)”;
- 常见 panic:
panic: sync: Cond.Signal with uninitialized mutex—— 很可能是Cond是零值(未用sync.NewCond初始化),或L指向了已释放/栈上分配的锁 - 静默失效:把
Cond放在局部变量里,函数返回后对象被回收,但还有 goroutine 在等它 - 锁类型错配:给
Cond传了*sync.RWMutex,却用mutex.Lock()而不是mutex.RLock()——Cond不关心读写锁语义,只认Locker接口,但你自己得保证加锁方式和业务逻辑一致 - 忘记在 Signal/Broadcast 后 unlock:如果在持有锁时通知,记得通知完再 unlock,否则唤醒的 goroutine 会被卡在抢锁上
复杂点在于:Cond 的正确性极度依赖程序员对“条件谓词”的精确定义和保护范围。它不像 channel 那样自带内存模型约束,稍一松懈,就会掉进竞态或唤醒丢失的坑里。










