sync.cond 必须与锁配合使用且 wait 必须置于 for 循环中,因存在虚假唤醒;signal/broadcast 应在锁内调用以确保状态修改原子性;不可与 rwmutex 混用,且 cond 实例不可复制。

sync.Cond 不能脱离锁独立工作,也不能直接“配合函数”使用——它只响应被锁保护的共享状态变化。所谓“配合”,本质是把 Wait、Signal 嵌入到业务函数中,并严格遵循锁 + 循环检查模式。
为什么 Wait 必须写在 for 循环里?
因为唤醒可能是虚假的(spurious wakeup),系统调度或 runtime 可能在没收到 Signal/Broadcast 时就让 goroutine 返回。Go 不保证每次 Wait 返回都对应真实状态变更。
- 错误写法:
if !ready { cond.Wait() }—— 一旦虚假唤醒,ready仍为 false,后续逻辑直接出错 - 正确写法:
for !ready { cond.Wait() }—— 每次醒来都重新检查,确保条件真成立 - 条件变量本身不存状态,
ready必须是受同一把*sync.Mutex保护的变量 - 即使你用
time.AfterFunc或信号量模拟唤醒,也得守这个循环规则
Signal 和 Broadcast 的调用时机与锁关系
Signal 和 Broadcast 本身不要求持有锁,但实践中几乎总该在锁内调用——因为你要先修改导致条件成立的状态,而该状态必须被锁保护。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 典型流程:
mu.Lock()→ 修改共享变量(如queue = append(queue, item))→cond.Signal()→mu.Unlock() - 如果在锁外改状态再调
Signal,其他 goroutine 可能在Wait返回后、读取状态前,被另一个 goroutine 覆盖掉新值 -
Broadcast开销略大,但比漏唤醒强得多;比如关闭服务时设closed = true,必须用Broadcast让所有等待者退出,否则卡死 - 别用
sync.RWMutex配sync.Cond——Cond的L字段只接受实现了Lock/Unlock的接口,而*sync.RWMutex的Lock方法签名和sync.Mutex不一致,运行时 panic
如何把 Cond 嵌入实际业务函数(例如消费者/生产者)
不是“用 Cond 包裹函数”,而是把 Wait 和 Signal 当作同步原语,插进函数的关键路径里。重点是状态读写与等待通知的原子边界。
- 消费者函数中:
mu.Lock()→for len(queue) == 0 { cond.Wait() }→ 取出queue[0]并queue = queue[1:]→mu.Unlock() - 生产者函数中:
mu.Lock()→queue = append(queue, item)→cond.Signal()→mu.Unlock() - 避免在
Wait返回后、mu.Unlock()前做耗时操作(如 HTTP 请求),否则阻塞其他协程抢锁 - 若一个函数既要等 A 条件又要等 B 条件,别复用同一个
Cond—— 应创建condA和condB,共用一把mu,否则Signal无法精准唤醒目标协程
最易被忽略的一点:Cond 实例创建后不能复制,也不能跨 goroutine 传递指针以外的东西;sync.Cond 是轻量包装,真正起作用的是你写的那几行 for + Lock + Wait —— 它不神奇,只是帮你把“释放锁→挂起→重获锁”这一串操作封装成一个原子调用。写错循环、漏锁、混用 RWMutex,都会立刻崩。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










