sync.Cond 的核心用途是等待某个条件成立,而非协调多个 goroutine;必须与 sync.Mutex 配合使用,条件检查和等待需在锁内进行,且须用 for 循环避免虚假唤醒。

sync.Cond 的核心用途不是“协调多个 goroutine”,而是“等待某个条件成立”
很多人误把 sync.Cond 当作通用同步工具,结果写出死锁或假唤醒泛滥的代码。它真正该用的场景是:一个或多个 goroutine 需要阻塞等待某个共享状态变量满足特定条件(比如缓冲区非空、队列有新任务),而这个条件由其他 goroutine 修改后主动通知。
在生产者消费者模型中,sync.Cond 必须和 sync.Mutex 绑定使用——条件判断和等待必须在锁保护下进行,否则存在竞态;Signal 或 Broadcast 本身不加锁,但通知前通常需确保状态已更新并解锁。
- 永远不要在未持有 mutex 的情况下调用
c.Wait(),会 panic -
c.Signal()只唤醒一个等待者,适合单消费者场景;c.Broadcast()唤醒全部,适合多消费者但需配合循环检查条件,避免虚假唤醒导致逻辑错乱 - Wait 内部会自动释放 mutex 并挂起 goroutine,被唤醒后重新获取 mutex 才返回——这意味着你必须在
for循环里检查条件,不能用if
生产者写入时如何安全通知消费者
典型错误是:生产者加锁 → 写入数据 → 调用 c.Signal() → 解锁。这看似合理,但若此时没有 goroutine 在 Wait,Signal 就丢失了;而消费者可能刚解锁准备 Wait,就错过通知(lost wakeup)。
正确做法是:确保通知发生在消费者已进入 Wait 状态之后,或通过状态变量 + 循环等待兜底。更稳妥的方式是“先改状态、再通知、最后解锁”:
立即学习“go语言免费学习笔记(深入)”;
mu.Lock() queue = append(queue, item) // 条件已满足:队列非空 c.Broadcast() // 或 Signal() mu.Unlock()
注意:Broadcast 不保证唤醒顺序,也不保证所有被唤醒者都能立刻消费成功——每个消费者醒来后仍需再次加锁、检查 len(queue) > 0,为空则继续 Wait。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 不要在锁内做耗时操作(如网络请求、文件读写),否则阻塞其他 goroutine
- 如果生产者频率极高,频繁 Broadcast 可能引发惊群效应,此时可考虑用 channel 替代,或限制唤醒频次
- 避免在 Wait 前忘记检查初始状态(例如启动时队列已非空),否则可能永久阻塞
消费者如何避免虚假唤醒和重复消费
sync.Cond.Wait() 可能被系统信号、调度器干扰等无理由唤醒(spurious wakeup),所以绝不能用 if queue == nil { c.Wait() },必须用 for 循环重检条件:
mu.Lock()
for len(queue) == 0 {
c.Wait()
}
item := queue[0]
queue = queue[1:]
mu.Unlock()
这段代码的关键在于:每次 Wait 返回都意味着重新持有了 mu,因此可以安全读写 queue;退出循环时,len(queue) > 0 是确定成立的。
- 切片截取后未清空底层数组引用?这不是
sync.Cond的问题,但会影响 GC,建议显式置零或使用copy分配新底层数组 - 如果消费者需要处理多个 item,别在一次锁内全取完——会阻塞生产者太久;应每次只取一个,释放锁后再循环
- 不要在 Wait 返回后直接操作共享数据而不加锁——Wait 返回时锁已重新持有,但后续任何访问都必须仍在锁保护下
与 channel 相比,什么情况下必须用 sync.Cond
Go 官方推荐优先使用 channel 实现生产者消费者,因为语义清晰、自带阻塞、无需手动管理锁。但 sync.Cond 不可替代的场景只有两个:
一是需要**对同一资源做多种条件等待**(比如既要等“有数据”,又要等“缓冲区未满”,还要等“关闭信号”),channel 难以优雅表达多路条件;二是**性能敏感且需精细控制唤醒粒度**(例如百万级 goroutine 中只唤醒特定类型消费者),channel 的 runtime 调度开销和内存分配在此类场景下成为瓶颈。
- 用
sync.Cond实现带优先级的消费者(如紧急任务插队)时,需额外维护优先队列和条件变量组合,复杂度陡增 - 调试困难:Wait/Broadcast 没有 trace 支持,出问题时难以定位哪个 goroutine 卡在哪儿,而 channel 可通过
runtime.Stack()或 pprof 观察阻塞点 - 一旦混用 channel 和
sync.Cond(比如用 channel 传信号、用 Cond 等状态),极易因锁顺序不一致引发死锁
真正要用 sync.Cond,就得接受它带来的责任:精确的锁管理、显式的条件循环、以及对虚假唤醒的持续警惕。它不是更高级的 channel,只是另一套更底层、更易出错的原语。

















