
sync.Cond 为什么不能单独用 Wait?
因为 Cond.Wait 会自动释放关联的 *sync.Mutex,并在唤醒后重新加锁——但前提是调用前必须已持有该锁。没加锁就调 Wait,运行时直接 panic:sync: Cond.Wait with uninitialized mutex 或更隐蔽的竞态崩溃。
常见错误是把 Cond 当成独立信号量,忘了它只是“条件等待辅助工具”,底层完全依赖外部锁来保护共享状态。
- 必须在
mu.Lock()之后、cond.Wait()之前完成所有状态检查(比如if !ready { cond.Wait() }) -
Wait返回时锁已重新获取,所以后续操作可安全读写共享变量 - 别用
sync.RWMutex配sync.Cond——Cond只接受*sync.Mutex
什么时候该用 Broadcast 而不是 Signal?
Signal 只唤醒一个等待协程,Broadcast 唤醒全部。选哪个不看“通知力度”,而看「状态变更是否对所有等待者都有效」。
比如实现一个资源池:当新资源归还时,只用 Signal 唤醒一个正在等资源的协程就够了;但如果关闭了整个池(状态变为不可用),就必须用 Broadcast 让所有等待者立刻退出,否则它们会永远卡在 Wait 上。
立即学习“go语言免费学习笔记(深入)”;
- 共享状态是“一次性消费”型(如单个任务分发)→ 优先
Signal - 共享状态是“全局生效”型(如停止标志、错误广播)→ 必须
Broadcast -
Broadcast开销略大,但比漏唤醒导致死锁强得多
为什么 Wait 要套在 for 循环里?
因为唤醒可能是虚假的(spurious wakeup)——系统或调度器在没收到 Signal/Broadcast 时也可能让协程返回。Go 不保证唤醒一定对应真实状态变化。
所以不能写 if !done { cond.Wait() },得写 for !done { cond.Wait() }。每次醒来都重新检查条件,确保逻辑正确。
- 即使你确定没虚假唤醒,Go 官方文档也明确要求循环检查
- 条件判断必须和
Wait在同一锁保护下,否则检查和等待之间可能被其他协程改掉状态 - 示例中常见的
for queue.Len() == 0 { cond.Wait() }就是典型安全写法
和 channel 比,sync.Cond 的实际优势在哪?
它不传输数据,只做信号通知,且能复用同一个锁保护多个条件变量。适合「状态复杂、等待条件多、且需精确控制唤醒时机」的场景。
比如一个服务同时管理连接数、内存水位、磁盘空间三个阈值,每个阈值触发不同行为——用 channel 得开三个通道,还得协调关闭;而用 sync.Cond 可共用一把锁,分别创建 connCond、memCond、diskCond,各自 Wait 和 Signal,互不干扰。
- channel 更适合“传递消息”或“解耦生产/消费”,
Cond更适合“同一线程/锁域内精细同步” -
Cond没缓冲区,不会堆积通知;channel 若无缓冲又没人收,发送会阻塞 - 调试时
Cond的等待者数量无法直接观察,容易漏掉未唤醒的协程
真正难的是设计好状态检查逻辑和唤醒时机——错一点,要么假唤醒忙等,要么真该醒却不醒,而且问题往往压测才暴露。










