MST

星途 面试题库

面试题:Go中条件变量如何与互斥锁配合使用

在Go语言中,描述条件变量(`sync.Cond`)与互斥锁(`sync.Mutex`)配合使用的基本流程,并给出一个简单示例代码说明如何利用它们实现线程间的同步,例如生产者 - 消费者模型场景。
15.4万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

基本流程

  1. 初始化
    • 首先创建一个 sync.Mutex 实例用于保护共享资源,确保同一时间只有一个 goroutine 可以访问共享资源。
    • 创建一个 sync.Cond 实例,其 NewCond 函数接受一个 Locker 接口类型参数,通常传入前面创建的 sync.Mutex 实例。这使得 sync.Cond 可以使用该互斥锁来同步访问条件相关的操作。
  2. 生产者流程
    • 生产者获取互斥锁,以确保对共享资源(如缓冲区)的安全访问。
    • 检查共享资源是否满足生产条件(例如缓冲区是否已满)。如果不满足条件,生产者调用 cond.Wait() 方法。这个方法会自动释放互斥锁并阻塞当前 goroutine,直到被其他 goroutine 通过 cond.Signal()cond.Broadcast() 唤醒。唤醒后,Wait() 方法会重新获取互斥锁。
    • 当条件满足时,生产者生产数据并放入共享资源。
    • 生产者释放互斥锁,并调用 cond.Signal()cond.Broadcast() 通知等待在条件变量上的消费者,有新的数据可用。Signal() 会唤醒一个等待的 goroutine,而 Broadcast() 会唤醒所有等待的 goroutine。
  3. 消费者流程
    • 消费者获取互斥锁,以确保对共享资源的安全访问。
    • 检查共享资源是否满足消费条件(例如缓冲区是否为空)。如果不满足条件,消费者调用 cond.Wait() 方法,同样会自动释放互斥锁并阻塞,直到被唤醒并重新获取互斥锁。
    • 当条件满足时,消费者从共享资源中取出数据。
    • 消费者释放互斥锁,并调用 cond.Signal()cond.Broadcast() 通知等待在条件变量上的生产者,有空间可以生产新的数据。

示例代码(生产者 - 消费者模型)

package main

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

// 定义缓冲区大小
const bufferSize = 3

// 共享缓冲区
var buffer [bufferSize]int
// 缓冲区当前元素数量
var count int
// 缓冲区下一个写入位置
var in int
// 缓冲区下一个读取位置
var out int

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

// 生产者函数
func producer(id int) {
    for {
        mu.Lock()
        for count == bufferSize {
            // 缓冲区已满,等待
            cond.Wait()
        }
        buffer[in] = id*10 + in
        fmt.Printf("Producer %d produced %d\n", id, buffer[in])
        in = (in + 1) % bufferSize
        count++
        mu.Unlock()
        cond.Signal()
        time.Sleep(time.Second)
    }
}

// 消费者函数
func consumer(id int) {
    for {
        mu.Lock()
        for count == 0 {
            // 缓冲区为空,等待
            cond.Wait()
        }
        val := buffer[out]
        fmt.Printf("Consumer %d consumed %d\n", id, val)
        out = (out + 1) % bufferSize
        count--
        mu.Unlock()
        cond.Signal()
        time.Sleep(time.Second)
    }
}
func main() {
    // 启动两个生产者
    go producer(1)
    go producer(2)
    // 启动两个消费者
    go consumer(1)
    go consumer(2)

    // 防止主函数退出
    select {}
}

在上述代码中:

  • 生产者和消费者都通过 sync.Mutex 来保护对共享缓冲区 buffer 的访问。
  • sync.Cond 用于在缓冲区满或空时,让生产者或消费者等待合适的条件,并在条件满足时通知其他 goroutine。
  • producer 函数不断生产数据并放入缓冲区,consumer 函数不断从缓冲区取出数据,从而实现了生产者 - 消费者模型的同步。