MST

星途 面试题库

面试题:Go中条件变量唤醒机制的基础实现

请描述Go语言中条件变量(`sync.Cond`)是如何与互斥锁配合实现唤醒机制的?简单举例说明在生产者 - 消费者模型里如何使用条件变量的唤醒机制。
30.3万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

1. sync.Cond 与互斥锁配合实现唤醒机制

在Go语言中,sync.Cond 依赖一个互斥锁(通常是 sync.Mutexsync.RWMutex)来工作。

  • 初始化:创建 sync.Cond 实例时,需要传入一个已经初始化的互斥锁。例如:
var mu sync.Mutex
cond := sync.NewCond(&mu)
  • 等待:调用 cond.Wait() 方法时,会自动解锁关联的互斥锁,并使当前 goroutine 进入等待状态。当该 goroutine 被唤醒时,Wait() 会重新获取互斥锁并返回。这样做是为了避免竞态条件,保证在等待期间共享资源不会被其他 goroutine 意外修改。
  • 唤醒:其他 goroutine 可以调用 cond.Signal() 方法唤醒一个等待的 goroutine,或者调用 cond.Broadcast() 方法唤醒所有等待的 goroutine。在调用 Signal()Broadcast() 之前,必须先获取互斥锁,以确保在修改共享状态后再进行唤醒操作,避免唤醒后等待的 goroutine 读取到不一致的状态。

2. 生产者 - 消费者模型里使用条件变量的唤醒机制示例

package main

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

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

func NewQueue() *Queue {
    q := &Queue{}
    q.cond = sync.NewCond(&q.mu)
    return q
}

func (q *Queue) Enqueue(item int) {
    q.mu.Lock()
    defer q.mu.Unlock()
    q.data = append(q.data, item)
    fmt.Printf("Produced: %d\n", item)
    q.cond.Broadcast()
}

func (q *Queue) Dequeue() int {
    q.mu.Lock()
    defer q.mu.Unlock()
    for len(q.data) == 0 {
        q.cond.Wait()
    }
    item := q.data[0]
    q.data = q.data[1:]
    fmt.Printf("Consumed: %d\n", item)
    return item
}

func main() {
    queue := NewQueue()

    var wg sync.WaitGroup
    wg.Add(2)

    go func() {
        defer wg.Done()
        for i := 0; i < 3; i++ {
            queue.Enqueue(i)
            time.Sleep(time.Second)
        }
    }()

    go func() {
        defer wg.Done()
        for i := 0; i < 3; i++ {
            queue.Dequeue()
            time.Sleep(time.Second * 2)
        }
    }()

    wg.Wait()
}

在上述代码中:

  • Queue 结构体包含一个数据切片 data、一个互斥锁 mu 和一个条件变量 cond
  • Enqueue 方法向队列中添加元素,添加后调用 cond.Broadcast() 唤醒所有等待的消费者。
  • Dequeue 方法从队列中取出元素。如果队列为空,调用 cond.Wait() 等待生产者添加元素。一旦被唤醒且队列有数据,就取出并返回元素。
  • main 函数中,启动一个生产者 goroutine 和一个消费者 goroutine 来演示整个过程。