跳转到内容
主菜单
主菜单
移至侧栏
隐藏
导航
首页
最近更改
随机页面
MediaWiki帮助
代码酷
搜索
搜索
中文(中国大陆)
外观
创建账号
登录
个人工具
创建账号
登录
未登录编辑者的页面
了解详情
贡献
讨论
编辑“︁
Go 条件变量
”︁(章节)
页面
讨论
大陆简体
阅读
编辑
编辑源代码
查看历史
工具
工具
移至侧栏
隐藏
操作
阅读
编辑
编辑源代码
查看历史
常规
链入页面
相关更改
特殊页面
页面信息
外观
移至侧栏
隐藏
您的更改会在有权核准的用户核准后向读者展示。
警告:
您没有登录。如果您进行任何编辑,您的IP地址会公开展示。如果您
登录
或
创建账号
,您的编辑会以您的用户名署名,此外还有其他益处。
反垃圾检查。
不要
加入这个!
= Go条件变量 = '''条件变量(Condition Variable)'''是Go语言并发编程中用于协调多个goroutine之间同步的重要机制。它通常与[[Go互斥锁|互斥锁(Mutex)]]配合使用,允许goroutine在特定条件不满足时进入等待状态,并在条件可能满足时被唤醒。条件变量提供了一种高效的方式来实现复杂的线程间通信模式。 == 概述 == 条件变量的核心思想是:当某个共享数据的条件不满足时,goroutine可以主动释放锁并进入等待状态;当其他goroutine修改了共享数据并使得条件可能满足时,它可以通知等待的goroutine重新检查条件。 在Go中,条件变量通过<code>sync.Cond</code>类型实现,它包含三个主要方法: * <code>Wait()</code> - 释放锁并挂起goroutine * <code>Signal()</code> - 唤醒一个等待的goroutine * <code>Broadcast()</code> - 唤醒所有等待的goroutine == 基本用法 == 以下是条件变量的基本使用模式: <syntaxhighlight lang="go"> package main import ( "fmt" "sync" "time" ) func main() { var m sync.Mutex c := sync.NewCond(&m) queue := make([]interface{}, 0, 10) removeFromQueue := func(delay time.Duration) { time.Sleep(delay) c.L.Lock() queue = queue[1:] fmt.Println("Removed from queue") c.L.Unlock() c.Signal() // 通知等待的goroutine条件可能已改变 } for i := 0; i < 10; i++ { c.L.Lock() for len(queue) == 2 { // 条件检查 c.Wait() // 等待条件满足 } fmt.Println("Adding to queue") queue = append(queue, struct{}{}) go removeFromQueue(1 * time.Second) c.L.Unlock() } } </syntaxhighlight> '''输出示例:''' <pre> Adding to queue Adding to queue Removed from queue Adding to queue Removed from queue Adding to queue ... </pre> 这个示例展示了典型的生产者-消费者模式,其中生产者(主goroutine)在队列满时等待,消费者(removeFromQueue goroutine)在移除元素后发出信号。 == 工作原理 == 条件变量的工作流程可以用以下状态图表示: <mermaid> stateDiagram [*] --> 获取锁 获取锁 --> 检查条件 检查条件 --> 条件满足: 是 检查条件 --> 等待: 否 等待 --> 被唤醒 被唤醒 --> 检查条件 条件满足 --> 执行业务逻辑 执行业务逻辑 --> 释放锁 释放锁 --> [*] </mermaid> 关键点: 1. 条件检查必须在持有锁的情况下进行 2. <code>Wait()</code>会自动释放锁并在返回前重新获取锁 3. 条件检查应该使用循环而不是if语句,以防止虚假唤醒 == 实际应用案例 == === 工作池模式 === 条件变量非常适合实现工作池,其中worker goroutine在没有任务时等待,在有新任务时被唤醒。 <syntaxhighlight lang="go"> type WorkerPool struct { mu sync.Mutex cond *sync.Cond tasks []Task } func (p *WorkerPool) AddTask(task Task) { p.mu.Lock() p.tasks = append(p.tasks, task) p.mu.Unlock() p.cond.Broadcast() // 通知所有worker } func (p *WorkerPool) Worker() { for { p.mu.Lock() for len(p.tasks) == { p.cond.Wait() } task := p.tasks[0] p.tasks = p.tasks[1:] p.mu.Unlock() task.Execute() } } </syntaxhighlight> === 有限资源分配 === 当需要限制同时访问某种资源的goroutine数量时,条件变量也非常有用: <syntaxhighlight lang="go"> type ResourcePool struct { mu sync.Mutex cond *sync.Cond inUse int maxSize int } func (p *ResourcePool) Acquire() { p.mu.Lock() defer p.mu.Unlock() for p.inUse >= p.maxSize { p.cond.Wait() } p.inUse++ } func (p *ResourcePool) Release() { p.mu.Lock() p.inUse-- p.mu.Unlock() p.cond.Signal() } </syntaxhighlight> == 最佳实践 == 1. '''总是使用循环检查条件''':防止虚假唤醒 <syntaxhighlight lang="go"> c.L.Lock() for !condition { c.Wait() } c.L.Unlock() </syntaxhighlight> 2. '''选择合适的通知机制''': * 使用<code>Signal()</code>当只有一个goroutine需要被唤醒时 * 使用<code>Broadcast()</code>当多个goroutine可能满足条件时 3. '''避免锁竞争''':在调用<code>Signal()</code>或<code>Broadcast()</code>前释放锁 4. '''考虑上下文''':在长时间等待时,考虑使用<code>context.Context</code>实现超时 == 数学原理 == 条件变量的正确性可以部分通过[[Hoare逻辑]]来验证。对于条件变量操作,有以下不变式: <math> \{P \land C\} \text{Wait()} \{P\} </math> <math> \{P\} \text{Signal()} \{C\} </math> 其中: * <math>P</math>是保护条件变量的不变式 * <math>C</math>是等待的条件 == 常见错误 == 1. '''不检查条件直接等待''':这可能导致丢失唤醒或无限等待 2. '''在持有锁时执行耗时操作''':这会降低并发性能 3. '''忘记调用Signal/Broadcast''':导致goroutine永远等待 4. '''错误的条件检查顺序''':应该在持有锁的情况下检查条件 == 性能考虑 == 条件变量相比通道(channel)在某些场景下性能更好,特别是: * 需要频繁通知多个等待者时 * 需要细粒度控制锁时 * 实现复杂同步模式时 然而,对于简单场景,通道通常是更安全的选择。 == 总结 == Go的条件变量提供了强大的goroutine同步机制,特别适合实现复杂的等待/通知模式。正确使用时,它可以构建高效且正确的并发程序。关键是要记住: * 总是使用循环检查条件 * 确保在正确的时机获取和释放锁 * 选择合适的通知方式 * 考虑使用更高级的抽象(如channel)如果它们更适合你的场景 通过掌握条件变量,你可以解决Go并发编程中的许多复杂同步问题。 [[Category:编程语言]] [[Category:Go]] [[Category:Go 并发编程]]
摘要:
请注意,所有对代码酷的贡献均被视为依照知识共享署名-非商业性使用-相同方式共享发表(详情请见
代码酷:著作权
)。如果您不希望您的文字作品被随意编辑和分发传播,请不要在此提交。
您同时也向我们承诺,您提交的内容为您自己所创作,或是复制自公共领域或类似自由来源。
未经许可,请勿提交受著作权保护的作品!
取消
编辑帮助
(在新窗口中打开)