Golang sync.Cond

在并发编程中,协调多个协程之间的操作是一项常见的需求。条件变量(Condition Variable)是用于解决多个协程之间复杂协调问题的一种机制。条件变量配合互斥锁(Mutex)使用,可以让一组协程等待某个条件成立后再继续进行,而不会一直消耗 CPU 资源进行忙等待。

本文将详细介绍 Golang 中 sync 包提供的 Cond 类型,并通过示例代码展示如何使用 Cond 进行协程同步。

条件变量的基本介绍

Cond 是条件变量(Condition Variable)的简称,主要有以下三个方法:

  1. Wait()

    • 当前协程在等待条件变量满足时阻塞。
    • Wait() 会自动释放关联的互斥锁,并将当前协程置于等待状态。
    • 一旦被唤醒,Wait() 会在返回前重新获得锁,以确保条件仍然满足。
  2. Signal()

    • 唤醒一个在该条件变量上等待的协程。
    • 如果有多个协程在等待,Signal() 只是唤醒其中一个。
  3. Broadcast()

    • 唤醒所有在该条件变量上等待的协程。

示例代码

以下是一个使用 Cond 编写的生产者-消费者模型示例代码:

package main

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

type Queue struct {
    items []int
    cond  *sync.Cond
}

func NewQueue() *Queue {
    return &Queue{
        items: make([]int, 0),
        cond:  sync.NewCond(&sync.Mutex{}),
    }
}

func (q *Queue) Enqueue(item int) {
    q.cond.L.Lock()
    q.items = append(q.items, item)
    q.cond.Signal() // 通知等待的消费者
    q.cond.L.Unlock()
}

func (q *Queue) Dequeue() int {
    q.cond.L.Lock()
    for len(q.items) == 0 {
        q.cond.Wait() // 等待 until 队列非空
    }
    item := q.items[0]
    q.items = q.items[1:]
    q.cond.L.Unlock()
    return item
}

func main() {
    q := NewQueue()

    // 消费者
    go func() {
        for {
            item := q.Dequeue()
            fmt.Println("Consumed:", item)
        }
    }()

    // 生产者
    for i := 1; i <= 10; i++ {
        fmt.Println("Producing:", i)
        q.Enqueue(i)
        time.Sleep(time.Second)
    }
}

代码详解

  1. 定义队列结构体

    type Queue struct {
        items []int
        cond  *sync.Cond
    }
    
    func NewQueue() *Queue {
        return &Queue{
            items: make([]int, 0),
            cond:  sync.NewCond(&sync.Mutex{}),
        }
    }
    

    这里定义了一个 Queue 结构体,包含一个 items 切片用于存储队列中的元素,以及一个条件变量 cond

  2. 入队操作

    func (q *Queue) Enqueue(item int) {
        q.cond.L.Lock()
        q.items = append(q.items, item)
        q.cond.Signal() // 通知等待的消费者
        q.cond.L.Unlock()
    }
    

    在入队操作中,先获取互斥锁,然后将元素添加到队列中,并通过 Signal() 方法通知等待的消费者。

  3. 出队操作

    func (q *Queue) Dequeue() int {
        q.cond.L.Lock()
        for len(q.items) == 0 {
            q.cond.Wait() // 等待 until 队列非空
        }
        item := q.items[0]
        q.items = q.items[1:]
        q.cond.L.Unlock()
        return item
    }
    

    在出队操作中,先获取互斥锁,然后检查队列是否为空。如果队列为空,则调用 Wait() 方法等待。队列非空时,取出队列头部的元素并返回。

  4. 生产者和消费者

    func main() {
        q := NewQueue()
    
        // 消费者
        go func() {
            for {
                item := q.Dequeue()
                fmt.Println("Consumed:", item)
            }
        }()
    
        // 生产者
        for i := 1; i <= 10; i++ {
            fmt.Println("Producing:", i)
            q.Enqueue(i)
            time.Sleep(time.Second)
        }
    }
    

    main 函数中,创建一个队列实例,启动一个消费者协程,不断从队列中取出元素并打印。主协程作为生产者,不断向队列中添加元素。

总结

通过条件变量 Cond 配合互斥锁 Mutex,我们可以轻松实现协程之间的同步与协调,避免忙等待,提高程序的效率和响应速度。本文通过一个简单的生产者-消费者模型示例,展示了如何在 Golang 中使用条件变量进行协程同步。希望本文能帮助你更好地理解和应用条件变量。

打 赏