sync.cond的signal/broadcast无需持锁,因仅操作内部等待队列而不访问共享状态;但唤醒后必须由goroutine用for循环重检条件,以防虚假唤醒或条件未真正满足。

sync.Cond 的通知不是“发消息”,而是“唤醒等待者”——必须配合锁使用,且 Signal 和 Broadcast 本身不检查条件是否真成立,只负责解除阻塞。
为什么调用 Signal 或 Broadcast 前不用持有锁?
这两个方法只操作内部的等待队列(notifyList),不访问被保护的共享状态,所以无需加锁。但这也意味着:它们无法保证唤醒后条件一定满足——是否满足,得靠被唤醒的 goroutine 自己重新检查。
-
Signal唤醒一个等待者(FIFO),适合“单个资源就绪”场景,比如一个缓冲区有新数据了,只需唤醒一个消费者 -
Broadcast唤醒全部等待者,适合“状态全局变更”场景,比如关闭信号、重置配置,但要注意惊群问题 - 若没有 goroutine 在
Wait,Signal/Broadcast会直接返回,不会报错,也不会积压通知
Wait 必须在 for 循环里调用,不能用 if
因为存在虚假唤醒(spurious wakeup):goroutine 可能在没收到 Signal 的情况下被系统唤醒。如果只用 if,唤醒后直接往下走,可能读到错误状态(比如 channel 仍为空、缓冲区仍满)。
- 正确写法:
for !condition { cond.Wait() } - 错误写法:
if !condition { cond.Wait() }—— 一旦虚假唤醒或条件被其他 goroutine 改变,逻辑就崩了 - 这个
for循环里的condition必须是受同一把锁保护的变量或状态
Wait 的原子性操作到底做了什么?
Wait 看似简单,实则完成三件事:释放锁 → 挂起 goroutine → 被唤醒后重新获取锁。这三步是原子的,不可拆分。
- 释放锁发生在挂起前,否则其他 goroutine 拿不到锁,无法修改条件、也无法调用
Signal - 被唤醒后立即尝试抢锁,抢到才继续执行;若抢不到,会继续阻塞在锁上,直到拿到
- 因此,
Wait返回时,你一定持有锁,可以安全读写共享数据 - 别在
Wait前后漏掉Lock/Unlock——Wait只管自己那部分,不帮你管全程
常见误用:把 cond 当成 channel 用
有人试图用 cond.Signal() 替代 ch ,这是危险的。channel 有缓冲、有传递语义;而 <code>cond 没有队列、不传值、不保序、不保送达。
- 多个
Signal连续调用,若无 goroutine 等待,全部丢失 —— 它不存通知,只管“此刻有没有人等着” - 无法区分“谁该被唤醒”,
Signal是随机选一个(FIFO),Broadcast是全叫,没有选择性 - 真正需要“通知+传参”的场景,应该用 channel;
sync.Cond只适合“状态已变,请重检”的轻量协调
最容易被忽略的一点:条件变量本身不保存任何状态,它只是个“叫醒服务台”。所有判断逻辑、状态更新、锁管理,都得你自己写清楚、写对。错一个环节,整个同步就失效,而且问题往往偶发、难复现。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











