首页 > 编程笔记 > Go语言笔记 阅读:2

Go语言sync.Cond实现线程同步(新手必看)

有时我们可能会遇到这样的场景,某项任务要在特定的条件成立时进行操作,如果这些条件未被满足,那么就需要等待,直到条件达成。

获取特定条件的方式可以是在 for 循环中不断做轮询,也可以是当条件满足时发出信号通知。显然第二种方法的效率更高,既然通道是用来收发数据的,当然也就可以用来收发信号了。

使用通道接收信号的代码如下:
signal := make(chan struct{})
go func() {
        ...
        <- signal
}()
但是这种方法适合一对一,不适合一对多的场景。

在 Java 中,可以利用等待/通知(wait/notify)机制来实现阻塞或者唤醒。在 Go 语言中,sync.Cond 也可以达到类似的效果。

sync.Cond 是一个条件变量类型,用于实现线程之间的同步。具体来说,sync.Cond 的作用是在某个协程中等待一个特定的事件发生,然后唤醒一个或多个正在等待的协程继续执行。

sync.Cond 与某个条件相关,这个条件需要一组协程协作来达成。sync.Cond 基于 Mutex 或 RWMutex 增加了一个通知队列。在条件不满足时,队列中的所有协程都会被阻塞;当满足一定的条件时就会从队列中唤醒一个或唤醒多个协程,从而解决在并发场景下等待/通知的问题。唤醒的方式有单个(Signal)和广播(Broadcast)两种。

sync.Cond 与 sync.WaitGroup 的区别在于 sync.WaitGroup 允许主协程等待确定数量的子协程完成任务,而 sync.Cond 则不关心协程的数量,只关心等待条件,它基于某个条件的变化来协调协程间的同步,任意多个协程都可以修改相关的条件,一旦条件发生变化,其他等待这个条件的协程会被唤醒或进行相应的操作。

sync.Cond 的常用方法及使用说明如下表所示:

表:sync.Cond 的常用方法及使用说明
方法 说明
func NewCond(l Locker) *Cond 创建一个 sync.Cond 对象,其中l是一个实现了 Locker 接口的参数(通常是 *sync.Mutex 或 *sync.RMutex),用于保护被访问的共享状态
func(c *Cond) Wait() 若一个协程调用了 sync.Cond 的 Wait() 方法,那么它会暂停执行并等待,直到收到特定的通知(通常是其他协程调用 sync.Cond 的 Signal() 或 Broadcast() 方法发出的通知)。该协程会被放入一个由 sync.Cond 维护的等待队列中,它在等待队列中保持阻塞状态,直到被唤醒。调用这个方法有个前提,即当前协程已经获得了互斥锁
func(c *Cond) Signal() 当调用 Signal() 方法时,从等待此 sync.Cond 的协程中选取一个协程(通常是等待时间最长的那一个)并唤醒它
func(c *Cond) Broadcast() 以广播的方式唤醒所有等待此 sync.Cond 的协程,让它们继续执行

使用 sync.Cond 的流程如下:
1) 创建一个互斥锁,并使用 sync.NewCond() 函数创建一个条件变量。关键代码如下:
var mu sync.Mutex
cond := sync.NewCond(&mu)

2) 在某个协程中等待一个特定的事件发生时,调用 cond.Wait() 方法来阻塞该协程,并释放互斥锁。关键代码如下:
mu.Lock()
for !condition { // 检查等待条件是否满足
   cond.Wait()
}
// 执行操作
mu.Unlock()

3) 在其他协程中,如果某个事件发生,可以调用 cond.Signal() 或 cond.Broadcast() 方法来唤醒一个或多个等待的协程。这通常意味着条件已经改变,唤醒的协程可以继续执行。关键代码如下:
mu.Lock()
condition = true
cond.Signal() // 或 cond.Broadcast()
mu.Unlock()
这样设计 Sync.Cond 是为了方便协程在不同的执行路径中有效地协作或同步。例如,一个生产者协程在生产了新的数据项后,可以调用 cond.Signal() 或 cond.Broadcast() 方法来唤醒一个或所有等待的消费者协程。

使用 sync.Cond 时有以下注意事项:

相关文章