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