Skip to content

Cond

Go sync.Cond 详解

sync.Cond 是 Go 的一个同步原语,它用于协调 Goroutine 之间的等待和通知机制。sync.Cond 常用于实现线程间的等待、通知模式,比如生产者-消费者模式。它允许 Goroutine 在某个条件下等待,当条件满足时,其他 Goroutine 可以通知等待中的 Goroutine 继续执行。

1. 基本概念

  • Condition Variable:条件变量是用于实现等待和通知的机制,Go 中通过 sync.Cond 提供了这一功能。
  • Wait():等待某个条件成立。如果条件不成立,当前 Goroutine 会阻塞,直到其他 Goroutine 通知它。
  • Signal():通知一个等待中的 Goroutine。
  • Broadcast():通知所有等待中的 Goroutine。

sync.Cond 依赖于 sync.Mutexsync.RWMutex 来实现同步,通常需要和这些锁配合使用。

2. sync.Cond 的方法

  • Wait():将调用者添加到等待队列中,并且释放锁,当前 Goroutine 会被阻塞,直到其他 Goroutine 调用 Signal()Broadcast() 来唤醒它。
  • Signal():唤醒一个等待中的 Goroutine。
  • Broadcast():唤醒所有等待中的 Goroutine。
  • NewCond():创建一个新的条件变量。

3. sync.Cond 的使用示例

go
package main

import (
    "fmt"
    "sync"
)

var (
    mu      sync.Mutex  // 互斥锁
    cond    = sync.NewCond(&mu)  // 创建条件变量
    counter int
)

func worker(id int) {
    mu.Lock()   // 获取锁
    defer mu.Unlock()
    
    for counter < 5 {
        fmt.Printf("Worker %d: Waiting, counter=%d\n", id, counter)
        cond.Wait()  // 等待条件满足
    }

    fmt.Printf("Worker %d: Done, counter=%d\n", id, counter)
}

func main() {
    var wg sync.WaitGroup

    // 启动多个 Goroutine
    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            worker(id)
        }(i)
    }

    // 主 Goroutine 增加 counter,并通知所有等待的 Goroutine
    mu.Lock()
    counter = 5
    cond.Broadcast()  // 通知所有等待的 Goroutine
    mu.Unlock()

    wg.Wait()
}

输出

Worker 1: Waiting, counter=0
Worker 2: Waiting, counter=0
Worker 3: Waiting, counter=0
Worker 1: Done, counter=5
Worker 2: Done, counter=5
Worker 3: Done, counter=5

在这个示例中:

  • worker 函数模拟了多个 Goroutine 等待某个条件满足,直到 counter 达到 5。
  • main 函数修改 counter 的值,并通过 cond.Broadcast() 唤醒所有等待的 Goroutine。

4. sync.Cond 的工作原理

  • 每个调用 Wait() 的 Goroutine 都会被阻塞并放入条件变量的等待队列中,直到另一个 Goroutine 调用 Signal()Broadcast() 来唤醒它们。
  • Wait() 会在阻塞之前释放锁,允许其他 Goroutine 获取锁。等条件满足后,Wait() 会重新获得锁并继续执行。
  • Signal()Broadcast() 用于通知等待队列中的一个或多个 Goroutine,条件已经满足。

5. sync.Condsync.Mutex 的结合

由于 sync.Cond 依赖于 sync.Mutexsync.RWMutex,因此通常会在条件变量等待时与这些锁一起使用。每次对共享资源的访问都需要先加锁,确保条件变量操作的安全。

6. Signal()Broadcast() 的区别

  • Signal():通知一个等待中的 Goroutine,唤醒它进行处理。
  • Broadcast():通知所有等待中的 Goroutine,使它们都可以继续执行。

通常,Signal() 用于通知一个 Goroutine 条件满足,而 Broadcast() 用于通知所有条件满足。

7. sync.Cond 的性能考虑

sync.Cond 在高并发的场景下非常有用,尤其是在生产者-消费者模式中,它能够减少无谓的 CPU 占用,并且协调不同 Goroutine 的工作。但是,使用条件变量时需要小心避免:

  • 虚假唤醒:即 Wait() 被唤醒时,条件未必已经满足。因此,通常会使用 for 循环来判断条件。
go
for counter < 5 {
    cond.Wait()
}

这种方式能确保即使发生虚假唤醒,条件不满足时 Goroutine 仍然会继续等待。

8. 常见场景

  • 生产者-消费者模式:生产者生产数据,消费者消费数据,消费者在没有数据时等待,生产者生产数据时通知消费者。
  • 管道通信:Goroutine 通过管道传递消息,某些 Goroutine 等待数据的到来。
  • 任务调度:一个 Goroutine 执行任务,其他 Goroutine 等待任务完成后执行后续操作。

9. 总结

  • sync.Cond 是 Go 提供的条件变量,允许 Goroutine 等待某个条件满足,直到其他 Goroutine 通知它们继续执行。
  • 它结合了 sync.Mutexsync.RWMutex 来实现互斥操作,用于协调 Goroutine 之间的执行顺序。
  • 使用 Wait()Signal()Broadcast(),可以实现复杂的同步和通信机制,尤其适用于生产者-消费者等模型。
  • 虚假唤醒 是常见问题,因此在使用 Wait() 时需要检查条件,而不仅仅是等待一次通知。

sync.Cond 是处理复杂同步问题的强大工具,特别适用于需要多个 Goroutine 之间等待和通知的场景。