sync.cond 的核心用途是等待共享状态被其他协程修改,而非直接等待条件成立;它不保存也不检查条件,必须配合 sync.mutex 用 for 循环重检条件,防止虚假唤醒和竞态。

sync.Cond 的核心用途不是“等条件成立”,而是“等某个共享状态被其他协程修改”
很多人一看到 sync.Cond 就想当然用来“等待 x == true”,结果发现死等、假唤醒、漏通知——根本原因是没理解它的设计契约:Cond 本身不保存条件,也不检查条件,它只负责在锁保护下挂起/唤醒 goroutine。条件是否满足,必须由你用外部变量(比如 ready)配合 sync.Mutex 手动判断。
典型错误是:直接 c.Wait() 后就执行业务逻辑,跳过条件重检。Go 官方文档明确说 Wait 返回时条件**不一定成立**(可能被虚假唤醒或竞争打断),所以必须用 for 循环包裹。
- 永远用
for !condition { c.Wait() },别用if -
Cond必须和一个*sync.Mutex绑定,且所有对条件变量的读写都必须在该锁内进行 -
Signal()只唤醒一个等待者,Broadcast()唤醒全部;选哪个取决于语义:是“有一个资源可用”还是“状态全局变了”
为什么不能单独用 sync.Cond,必须配 sync.Mutex?
sync.Cond 的 Wait 方法会自动释放关联的 mutex,并在被唤醒后重新获取它。这个“释放→挂起→唤醒→重锁”原子序列,是避免竞态的关键。如果不用 mutex 保护条件变量,两个 goroutine 可能同时读到旧值、同时进入 Wait,然后谁都收不到通知。
常见坑:把 Cond 和多个不同 mutex 混用,或者在 Wait 外部读条件变量(没锁保护)。这会导致条件判断和挂起之间存在时间窗口,漏掉状态变更。
- 初始化:
c := sync.NewCond(&sync.Mutex{}),注意传的是指针 - 所有访问条件变量(如
dataReady)的地方,必须先mu.Lock()/mu.Unlock() -
c.Wait()内部会 unlock,返回前自动 lock,所以调用前必须已持有锁
Wait 返回后一定要重新检查条件,哪怕只发了一次 Signal
Go 运行时允许虚假唤醒(spurious wakeup),而且 Signal 可能唤醒了还没来得及进入 Wait 的 goroutine(比如刚解锁、还没调用 Wait),导致唤醒丢失。所以靠“发一次 signal 就一定唤醒一个 wait”来设计逻辑,迟早出问题。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
下面这个例子看似合理,实则危险:
mu.Lock()
if !dataReady {
c.Wait() // ❌ 错!这里 if 应该是 for
}
// 处理 data...
mu.Unlock()
正确写法:
mu.Lock()
for !dataReady {
c.Wait()
}
// 此时 dataReady 为 true,且 mu 已锁住
processData()
dataReady = false
mu.Unlock()
- 虚假唤醒是合法行为,运行时不保证不发生,代码必须容忍
- 即使你确定只用
Broadcast,也要用for,因为唤醒时机和条件检查时机无法严格对齐 - 条件变量最好用布尔或整数,避免用复杂结构体字段,否则容易漏判
替代方案:什么情况下不该用 sync.Cond?
如果你只是想“等一个事件发生”,比如“等另一个 goroutine 完成某事”,sync.WaitGroup 更简单安全;如果要“等某个 channel 有值”,直接 select + case 更自然;如果要做超时等待、带优先级的通知,<code>time.AfterFunc 或 context.WithTimeout 配合 channel 更可控。
sync.Cond 真正适合的场景很窄:多个 goroutine 在同一锁保护下,反复等待同一个共享状态变化(比如生产者-消费者模型中的缓冲区非空/非满),且需要精确控制唤醒粒度(只唤醒一个消费者,而不是全唤醒)。
- 不要用
Cond实现一次性通知(用sync.Once或 channel) - 不要在无锁上下文中调用
Wait(panic) - 不要跨包暴露未加锁的
Cond和其关联的条件变量
最常被忽略的一点:cond 的生命周期必须长于所有使用它的 goroutine。别在函数栈上 new Cond 然后 spawn goroutine 去 Wait——函数返回后 cond 可能被回收,引发不可预测行为。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










