sync.cond核心使用模式是:加锁→for循环检查条件→不满足则wait(自动释放并重获锁)→满足后执行操作→解锁;必须用for而非if防虚假唤醒和竞态,signal唤醒一个,broadcast唤醒全部,且须先改状态再通知。

sync.Cond 的核心使用模式是什么
直接用 sync.Cond 等待条件成立,必须配合互斥锁(sync.Mutex 或 sync.RWMutex)——它本身不提供任何同步语义,只负责“挂起”和“唤醒”协程。常见错误是只调用 Cond.Wait() 却没在调用前加锁,或在等待返回后没重新检查条件,导致虚假唤醒漏处理。
典型流程是:加锁 → 检查条件 → 不满足则 Cond.Wait()(自动释放锁并挂起)→ 被唤醒后重新加锁 → 再次检查条件 → 满足才继续。
示例逻辑:
mu.Lock()
for !condition {
cond.Wait() // 自动 unlock,唤醒时自动 re-lock
}
// 此处 condition 为 true,且 mu 已 lock
doWork()
mu.Unlock()
为什么必须用 for 循环检查条件,不能用 if
因为 Cond.Signal() 和 Cond.Broadcast() 唤醒的协程可能被调度延迟,而此时条件已被其他协程修改;也可能发生虚假唤醒(spurious wakeup),即没有被显式通知却从 Wait() 返回。Go 的 runtime 不保证唤醒一定对应一次通知。
所以必须用 for 循环重检,而不是 if 判断:
-
if !condition { cond.Wait() }—— 错误:唤醒后不检查,可能操作在无效状态上 -
for !condition { cond.Wait() }—— 正确:每次返回都确认条件成立
这也是官方文档强调的唯一安全写法。
Cond.Signal() 和 Cond.Broadcast() 的实际差异在哪
Cond.Signal() 最多唤醒一个正在等待的 goroutine(如果有多个,由 runtime 选一个);Cond.Broadcast() 唤醒所有等待者。二者都不保证唤醒顺序,也不保证立即执行——只是把 goroutine 从等待队列移到可运行队列。
选择依据取决于业务语义:
- 生产者每次只生成一个任务,消费者只需一个被唤醒 → 用
Signal() - 条件变化影响所有等待者(比如关闭信号、资源重置)→ 用
Broadcast() - 用
Broadcast()但只有一个 goroutine 能真正处理成功?没问题,其余 goroutine 在for循环里会再次检查失败并继续等待
注意:Broadcast() 不会导致惊群(thundering herd)问题恶化,因为每个被唤醒的 goroutine 仍需竞争锁并重检条件。
初始化 sync.Cond 时容易忽略什么
必须用 sync.NewCond(&mu) 创建,其中 &mu 是指向已声明的 sync.Mutex 或 sync.RWMutex 的指针。不能传值、不能复用锁实例给多个 Cond(虽不报错但行为未定义)、也不能在锁已销毁后继续使用 Cond。
常见坑:
- 写成
sync.NewCond(mu)(传值)→ 编译失败,因为sync.Locker接口要求指针方法 - 在函数内声明
mu sync.Mutex,然后defer mu.Unlock(),但Cond生命周期超出该函数 → 悬空引用,后续Wait()行为不可预测 - 多个
Cond共享同一个锁但监听不同条件 → 可以,但需确保每个Wait()的循环检查逻辑独立、无干扰
最稳妥的做法是把 sync.Cond、关联的锁、以及受保护的条件变量,封装在同一结构体中,避免生命周期和作用域错位。











