Go并发系列:2基本同步原语-2.3 Cond(条件变量)

427 阅读3分钟

2.3 Cond(条件变量)

在并发编程中,某些情况下我们需要一种机制,使得 goroutine 能够在满足特定条件时进行等待,并在条件满足后被唤醒。Go语言中的 sync.Cond 提供了这种机制,它通过条件变量实现 goroutine 之间的协调。下面我们详细介绍 Cond 的概念、使用方法及示例。

2.3.1 什么是Cond

Cond 是 Go 标准库 sync 包中的一种同步原语,代表条件变量。条件变量使得一个 goroutine 可以等待某个条件满足,而其他 goroutine 可以在条件满足后唤醒它们。Cond 一般与互斥锁 (Mutex) 一起使用,以保证条件检查和条件等待的原子性。

2.3.2 Cond的基本方法

Cond 主要有以下三个方法:

  • Wait:使当前 goroutine 等待条件变量,直到被唤醒。调用 Wait 前必须先锁定关联的互斥锁,Wait 方法会在等待时解锁互斥锁,并在返回前重新锁定它。
  • Signal:唤醒一个正在等待条件变量的 goroutine。
  • Broadcast:唤醒所有正在等待条件变量的 goroutine。

2.3.3 Cond的使用方法

使用 Cond 需要创建一个 sync.Cond 实例,并传入一个互斥锁。以下是基本使用示例:

package main

import (
    "fmt"
    "sync"
    "time"
)

var (
    mu      sync.Mutex
    cond    = sync.NewCond(&mu)
    ready   = false
)

func waitCondition(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    
    cond.L.Lock()
    for !ready {
        cond.Wait()
    }
    fmt.Printf("Goroutine %d ready\n", id)
    cond.L.Unlock()
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go waitCondition(i, &wg)
    }

    time.Sleep(1 * time.Second)
    cond.L.Lock()
    ready = true
    cond.L.Unlock()
    cond.Broadcast()

    wg.Wait()
    fmt.Println("All goroutines ready")
}

在这个示例中,五个 goroutine 会等待 ready 变量变为 true。当 ready 被设置为 true 后,调用 Broadcast 唤醒所有等待的 goroutine。

2.3.4 Cond的应用场景

Cond 适用于以下场景:

  1. 生产者-消费者问题:生产者生产数据,消费者消费数据,使用 Cond 可以协调生产者和消费者的操作。
  2. 线程池管理:在线程池中,工作线程可以等待任务的到来,并在任务到来时被唤醒。
  3. 事件通知系统:在事件驱动系统中,事件的发生可以通过 Cond 通知等待的 goroutine。

2.3.5 示例代码

以下是一个使用 Cond 实现简单生产者-消费者模型的示例:

package main

import (
    "fmt"
    "sync"
    "time"
)

var (
    mu      sync.Mutex
    cond    = sync.NewCond(&mu)
    queue   []int
    maxSize = 5
)

func produce(wg *sync.WaitGroup, id int) {
    defer wg.Done()
    
    for i := 0; i < 10; i++ {
        cond.L.Lock()
        for len(queue) == maxSize {
            cond.Wait()
        }
        queue = append(queue, i)
        fmt.Printf("Producer %d produced: %d\n", id, i)
        cond.L.Unlock()
        cond.Signal()
        time.Sleep(100 * time.Millisecond)
    }
}

func consume(wg *sync.WaitGroup, id int) {
    defer wg.Done()
    
    for i := 0; i < 10; i++ {
        cond.L.Lock()
        for len(queue) == 0 {
            cond.Wait()
        }
        item := queue[0]
        queue = queue[1:]
        fmt.Printf("Consumer %d consumed: %d\n", id, item)
        cond.L.Unlock()
        cond.Signal()
        time.Sleep(150 * time.Millisecond)
    }
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 2; i++ {
        wg.Add(1)
        go produce(&wg, i)
    }

    for i := 1; i <= 2; i++ {
        wg.Add(1)
        go consume(&wg, i)
    }

    wg.Wait()
    fmt.Println("All tasks completed")
}

在这个示例中,生产者 goroutine 和消费者 goroutine 使用 Cond 进行协调,以确保生产者不会在队列已满时继续生产,消费者不会在队列为空时继续消费。

结论

Cond 提供了一种高效的机制来协调多个 goroutine 的执行,使得它们能够在满足特定条件时进行等待和被唤醒。通过合理使用 Cond,可以简化并发程序的设计,确保数据一致性和并发性能。在实际应用中,需要根据具体需求选择合适的同步原语,以达到最佳的性能和安全性。在接下来的章节中,我们将继续探讨其他同步原语和并发编程技巧,帮助您更好地掌握 Go 的并发编程。