在并发编程中,我们经常需要在多个goroutine之间同步操作,特别是当某些操作需要等待特定条件发生时。Go语言的sync
包提供了一个强大的同步原语——条件变量(Cond
),它允许我们等待或通知某个条件的变化。
sync.NewCond
sync.NewCond
函数用于创建一个新的条件变量,并将其与一个互斥锁(sync.Mutex
或sync.RWMutex
)关联。这个条件变量可以用来挂起和唤醒goroutine,以便在满足特定条件时协调它们的执行。
条件变量的方法
L
:条件变量关联的互斥锁。Wait()
:当前goroutine在调用此方法后会被挂起,直到被Signal
或Broadcast
唤醒。Signal()
:唤醒一个等待条件变量的goroutine(如果有的话)。Broadcast()
:唤醒所有等待条件变量的goroutine。
示例代码
下面是一个使用sync.NewCond
的示例,模拟了一个比赛场景,其中10个选手需要等待一个开始信号才能开始比赛。
package main
import (
"fmt"
"sync"
"time"
)
func main() {
race()
}
// race 函数模拟了一个比赛场景,10个选手等待一个信号开始比赛
func race() {
// 创建一个条件变量和互斥锁
cond := sync.NewCond(&sync.Mutex{})
// 创建一个等待组,用于等待所有选手完成比赛
var wg sync.WaitGroup
// 初始化等待组,添加11个任务(10个选手 + 1个发令员)
wg.Add(11)
// 启动10个选手的goroutine
for i := 0; i < 10; i++ {
go func(num int) {
// 选手完成后,将等待组计数器减一
defer wg.Done()
// 打印选手就位信息
fmt.Println(num, "号已经就位")
// 选手获取互斥锁,准备等待信号
cond.L.Lock()
// 选手等待条件变量的信号
cond.Wait()
// 打印选手开始工作的信息
fmt.Println(num, "号已开始工作")
// 选手释放互斥锁
cond.L.Unlock()
}(i)
}
// 等待2秒,模拟比赛准备时间
time.Sleep(2 * time.Second)
// 启动一个goroutine,用于发送比赛开始的信号
go func() {
// 完成后,将等待组计数器减一
defer wg.Done()
// 打印准备开始的信息
fmt.Println("准备开始!")
// 发送广播信号,通知所有等待的选手开始比赛
cond.Broadcast()
}()
// 等待所有选手完成比赛
wg.Wait()
}
工作原理
在这个示例中,我们首先创建了一个条件变量cond
,并将其与一个新的sync.Mutex
关联。然后,我们启动了10个goroutine来模拟选手,每个选手在就位后会调用cond.Wait()
来挂起自己。这会导致它们释放互斥锁并等待信号。
主goroutine等待2秒钟后,启动一个发令员goroutine,它调用cond.Broadcast()
来唤醒所有等待的选手。当选手们被唤醒后,它们会重新获取互斥锁,打印开始工作的信息,然后释放互斥锁。
应用场景
条件变量非常适合用于需要等待特定事件或条件发生的场景,例如:
- 生产者-消费者问题,消费者等待生产者生产数据。
- 信号量控制,限制同时访问某个资源的goroutine数量。
- 任何需要在多个goroutine之间同步执行的场景。
总结
sync.NewCond
是Go语言中一个非常实用的同步原语,它提供了一种简单而有效的方式来协调多个goroutine的执行。通过使用条件变量,我们可以编写出更加灵活和高效的并发程序。在处理复杂的并发问题时,sync.NewCond
是一个不可或缺的工具。