Golang如何使用条件变量进行 Goroutine 的同步?

条件变量(Cond)是Go语言提供的一种同步原语,它用于等待或通知某些事件的发生。当某个事件尚未发生时,一个或多个goroutine可以通过条件变量等待该事件的发生;当事件发生时,某个goroutine可以通过条件变量通知其他等待该事件发生的goroutine。

在Go语言中,条件变量由sync包提供,其类型为sync.Cond,它包含以下几个方法:

func (c *Cond) Broadcast(): 唤醒所有等待该条件变量的goroutine。
func (c *Cond) Signal(): 唤醒等待该条件变量的某一个goroutine。
func (c *Cond) Wait(): 等待该条件变量的通知,并解锁当前的互斥锁,直到被唤醒后重新加锁。

下面是一个使用条件变量实现生产者-消费者模型的示例代码:

package main

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

type Queue struct {
    mu      sync.Mutex
    cond    *sync.Cond
    content []int
}

func NewQueue() *Queue {
    q := &Queue{content: make([]int, 0)}
    q.cond = sync.NewCond(&q.mu)
    return q
}

func (q *Queue) Push(val int) {
    q.mu.Lock()
    defer q.mu.Unlock()
    q.content = append(q.content, val)
    q.cond.Signal() // 通知等待该条件变量的一个goroutine
}

func (q *Queue) Pop() int {
    q.mu.Lock()
    defer q.mu.Unlock()
    for len(q.content) == 0 {
        q.cond.Wait() // 等待该条件变量的通知,并解锁当前的互斥锁
    }
    val := q.content[0]
    q.content = q.content[1:]
    return val
}

func main() {
    queue := NewQueue()
    var wg sync.WaitGroup
    wg.Add(2)

    go func() { // 生产者
        for i := 0; i < 10; i++ {
            queue.Push(i)
            time.Sleep(time.Millisecond * 500)
        }
        wg.Done()
    }()

    go func() { // 消费者
        for i := 0; i < 10; i++ {
            val := queue.Pop()
            fmt.Println(val)
            time.Sleep(time.Millisecond * 1000)
        }
        wg.Done()
    }()

    wg.Wait()
}

在上面的示例代码中,Queue是一个线程安全的队列类型,它包含一个互斥锁和一个条件变量cond。Push方法用于往队列中添加元素,Pop方法用于从队列中取出元素。在Pop方法中,如果队列为空,则调用cond.Wait()方法等待通知,并解锁当前的互斥锁,直到被Push方法唤醒后重新加锁。在Push方法中,每次往队列中添加元素后,调用cond.Signal()方法唤醒等待该条件变量的一个goroutine。

在main函数中,启动两个goroutine,一个用于生产元素,一个用于消费元素。