当前位置: 首页 > news >正文

条件变量sync.Cond

下面来详细拆解 sync.Cond 条件变量。


一、什么是条件变量,它解决什么问题?

互斥锁 (sync.Mutex) 解决的是互斥进入临界区的问题,但它本身没法表达 “等待某个条件成立” 这种语义。
当 goroutine 发现条件不满足时,一般只能两种做法:

  1. 忙等:循环检查条件占用 CPU;
  2. 定时休眠,但总会有不必要的延迟或唤醒。

条件变量就是为了高效解决这个问题:
在互斥锁保护下,检查条件;条件不满足时原子地释放锁并挂起 goroutine;条件满足时被唤醒,重新获取锁,继续执行。

sync.Condsync.Mutexsync.RWMutex 配合,提供 Wait / Signal / Broadcast 三个方法,实现这种等待/通知机制。


二、创建与接口

type Cond struct {noCopy noCopy       // go vet 禁止复制L      Locker       // 关联的锁,通常是 *Mutex 或 *RWMutexnotify notifyList   // 内部等待链表checker copyChecker // 运行时复制检测(go vet 静态分析也会查)
}

通过 sync.NewCond(l Locker) 创建,参数 l 必须是 Locker 接口(实现 Lock()Unlock()),一般传 *sync.Mutex*sync.RWMutex

  • *sync.Cond 是唯一的合理使用方式,禁止复制值
  • 内部持有 goroutine 等待队列;复制会导致独立的等待队列,原、副本的 Signal/Broadcast 无法唤醒对方队列中的 goroutine。

三、核心方法

1. Wait() —— 等待条件满足

调用时必须持有锁(L.Lock() 已调用),否则会 panic(Go 1.6+)。

Wait() 执行以下原子操作:

  1. 将当前 goroutine 加入该 Cond 的等待队列;
  2. 调用 L.Unlock() 释放锁;
  3. 阻塞当前 goroutine,等待被唤醒;
  4. 被唤醒后,重新尝试获取锁(调用 L.Lock());
  5. Wait() 返回时,锁已被重新持有,goroutine 继续检查条件。

标准使用模式(必须在 for 循环中检查条件,不能使用 if):

mu.Lock()
for !condition() {cond.Wait()
}
// 此时条件满足,并持有锁
// 临界区代码 ...
mu.Unlock()

为什么必须用 for 而不是 if

  • 可能发生虚假唤醒(即使没有 Signal),Go 虽暂无此情况,但符合 POSIX 传统;
  • 被唤醒后,其他 goroutine 可能抢在自身之前进入临界区并改变了条件;
  • for 重新检查条件可保证逻辑正确。

2. Signal() —— 唤醒一个等待的 goroutine

唤醒等待队列中的一个 goroutine(如果有)。
调用 Signal 时不需要持有锁(但通常建议在持有锁的临界区内调用,以避免丢失唤醒)。

典型场景:生产者生产一个数据后,通知一个消费者。

3. Broadcast() —— 唤醒所有等待的 goroutine

唤醒等待队列中的所有 goroutine。
同样调用时不要求持有锁,但一般在修改了可能导致很多等待者条件成立的共享状态后调用。

典型场景:全局状态变更,所有等待者的条件都需要重新评估(比如退出信号、缓存被清空等)。


四、内部实现原理(简明版)

sync.Cond 基于运行时内部的 notifyList 实现,本质上是一个信号量 + 等待队列

  • notifyList 保存 goroutine 链表,包含 wait(等待计数器)和 notify(通知计数器)。
  • Wait() 增加 wait,将 goroutine 挂起(gopark),等待通知。
  • Signal() 增加 notify,唤醒队首 goroutine(如果 notify < wait)。
  • Broadcast()notify 设为 wait,唤醒全部。

释放锁和挂起是原子地完成(防止错过通知),内部使用原子操作和运行时调度器协作。

自旋和饥饿模式不涉及 Cond,因为 Cond 直接使用的是锁和底层的 semaphore 式等待。


五、典型用法示例

示例 1:生产者-消费者队列(固定容量)

package mainimport ("fmt""sync"
)type Queue struct {mu    sync.Mutexitems []intcap   intcond  *sync.Cond
}func NewQueue(capacity int) *Queue {q := &Queue{cap: capacity}q.cond = sync.NewCond(&q.mu)return q
}func (q *Queue) Put(item int) {q.mu.Lock()defer q.mu.Unlock()for len(q.items) == q.cap { // 队列满则等待q.cond.Wait()}q.items = append(q.items, item)q.cond.Signal() // 通知一个等待的取操作
}func (q *Queue) Take() int {q.mu.Lock()defer q.mu.Unlock()for len(q.items) == 0 { // 队列空则等待q.cond.Wait()}item := q.items[0]q.items = q.items[1:]q.cond.Signal() // 通知一个等待的放操作return item
}

示例 2:等待某个条件一次性满足(如初始化完成)

var (initialized boolmu          sync.Mutexcond        = sync.NewCond(&mu)
)func initResource() {mu.Lock()// 初始化...initialized = truemu.Unlock()cond.Broadcast() // 通知所有等待者
}func useResource() {mu.Lock()for !initialized {cond.Wait()}// 使用资源...mu.Unlock()
}

注意,这里 Broadcast 放在 Unlock 之后调用也可以,但在 Unlock 之前调用通常更安全,避免某些竞态;两种都可以,根据语义选择。


六、常见陷坑与最佳实践

要点 说明
必须持有锁再 Wait Go 会检查并可能 panic,忘了会导致数据竞争或死锁
Wait 必须在循环里 for 而非 if,防止虚假唤醒和条件变化
不要复制 Cond sync.CondnoCopy,传递只能用指针
Signal/Broadcast 并非必须加锁 调用可无锁,但为清晰通常放在临界区内,避免错过唤醒
与 Channel 的抉择 Channel 更简单直接;但当需要反复检查复杂条件且互斥操作多时,Cond 更灵活
sync.Once 区分 Once 是一次性初始化;Cond 用于可反复变化的条件等待

七、总结

  • sync.Cond 是 Go 的标准条件变量实现,与互斥锁配合实现 “等待条件成立” 的同步模型。
  • Wait 原子解锁、挂起,被唤醒后重新加锁返回,必须在循环中使用。
  • Signal 唤醒一个,Broadcast 唤醒全部。
  • 不可复制,使用指针传递。
  • 适合需要基于复杂条件协调 goroutine 的场景,如资源池、有界队列、状态依赖的执行等。
http://www.jsqmd.com/news/809601/

相关文章:

  • 为什么 Gold Answer 在 GraphRAG 系统中越来越不重要了
  • 从蛋白质分类到点云处理:Graph Pooling在不同领域的实战配置与调参心得
  • 终极指南:3步免费解锁Cursor AI编辑器完整Pro功能
  • 别再只盯着信号格了!手把手教你用手机工程模式看懂MCC、BAND、RSRP这些‘天书’
  • 如何在Windows上轻松安装安卓应用:告别臃肿模拟器的终极指南
  • 通过Taotoken模型广场快速选型并体验不同大模型效果
  • 无锡及周边抖音运营公司排行:聚焦中小企拓客实效对比 - 速递信息
  • 如何快速将Figma设计文件转换为结构化JSON数据:完整指南
  • 携程卡回收 / 订民宿实操指南:任我行 / 任我游用卡细节 - 喵权益卡劵助手
  • Windows安卓应用安装终极指南:APK Installer让安装变得如此简单
  • 从‘设备描述符请求失败’看USB协议:你的数据线、扩展坞和主板接口都达标了吗?
  • 终极音乐解锁指南:3步让加密音频随处可听
  • 云代理商:混合云架构下的 Hermes Agent 部署 本地 + 云端的最佳实践
  • 2026最新十大高清免费图片素材网站推荐(含图库+找图片素材全攻略) - 品牌2026
  • ElevenLabs有声书项目踩坑实录(内部技术白皮书节选):时序偏移修复、跨语种混读断句、版权语音水印嵌入三重攻坚
  • Firefly:一站式大模型训练工具,从零到一掌握LLM微调
  • 飞书应用自动化配置:基于DrissionPage与OpenAPI的混合架构实践
  • 半导体行业数据共享的价值与挑战:从WSTS机制看巨头退出影响
  • Approximate Dynamic Programming(近似动态规划算法)第六章:策略工具箱的实战选择与融合指南
  • Proteus元件库别再瞎找了!这份保姆级中英文对照表+分类指南,让你5分钟精准定位
  • 2026上海阿里云企业邮箱采购,靠谱服务商推荐及收费标准 - 品牌2025
  • 35款PowerBI可视化模板:让数据分析师轻松打造专业级报表
  • 2026年无锡GEO优化与AI搜索优化全攻略:制造业精准获客实战指南与本地服务商权威推荐 - 优质企业观察收录
  • BilibiliVideoDownload使用全攻略:从零开始到下载高手
  • 2026 海口名表回收避坑|5 家平台实测,这家最安全 - 奢侈品回收测评
  • 从视差到三维:深度图与点云生成的核心原理与实战解析
  • 百度文库免费下载终极指南:3分钟快速获取完整文档的简单方法
  • 通过OpenClaw CLI一键配置Taotoken接入Agent工作流
  • 2026年无锡GEO优化与AI搜索引擎优化服务商深度评测:制造业数字获客的五强对比 - 优质企业观察收录
  • 2026年无锡GEO优化与AI搜索优化:制造业精准获客完全指南 - 优质企业观察收录