面试题答案
一键面试sync.Cond的Wait方法实现机制
- 释放Mutex:当调用
sync.Cond
的Wait
方法时,它首先会释放与该Cond
关联的Mutex
。这是通过调用runtime_notifyListWait
函数来实现的,该函数会将当前 goroutine 加入到等待队列中,并在加入队列前释放Mutex
。这一步非常关键,因为如果不释放Mutex
,其他 goroutine 将无法获取Mutex
进而修改共享状态,导致死锁。 - 等待唤醒:释放
Mutex
后,当前 goroutine 会进入睡眠状态,等待被其他 goroutine 通过Cond
的Signal
或Broadcast
方法唤醒。 - 重新获取Mutex:当被唤醒时,
Wait
方法会重新获取之前释放的Mutex
。在重新获取Mutex
成功后,Wait
方法才会返回,使得当前 goroutine 可以安全地访问共享资源。
实际应用场景
- 生产者 - 消费者模型:在生产者 - 消费者模型中,当缓冲区已满时,生产者需要等待,直到消费者从缓冲区中取出数据,使缓冲区有空间。同样,当缓冲区为空时,消费者需要等待,直到生产者向缓冲区中放入数据。
package main
import (
"fmt"
"sync"
)
type Buffer struct {
data []int
size int
count int
mutex sync.Mutex
cond *sync.Cond
}
func NewBuffer(size int) *Buffer {
b := &Buffer{
data: make([]int, 0, size),
size: size,
count: 0,
}
b.cond = sync.NewCond(&b.mutex)
return b
}
func (b *Buffer) Produce(item int) {
b.mutex.Lock()
for b.count == b.size {
b.cond.Wait()
}
b.data = append(b.data, item)
b.count++
fmt.Printf("Produced: %d\n", item)
b.cond.Signal()
b.mutex.Unlock()
}
func (b *Buffer) Consume() int {
b.mutex.Lock()
for b.count == 0 {
b.cond.Wait()
}
item := b.data[0]
b.data = b.data[1:]
b.count--
fmt.Printf("Consumed: %d\n", item)
b.cond.Signal()
b.mutex.Unlock()
return item
}
在上述代码中,Produce
和 Consume
方法使用 sync.Cond
的 Wait
方法来处理缓冲区满和缓冲区空的情况,确保了生产者和消费者之间的正确同步。
不使用Wait方法的问题
- 忙等待(Busy Waiting):如果不使用
Wait
方法,生产者和消费者可能会采用忙等待的方式来检查缓冲区的状态。例如,生产者可能会不断循环检查缓冲区是否有空间,消费者可能会不断循环检查缓冲区是否有数据。这会浪费大量的 CPU 资源,因为在等待期间,goroutine 一直在消耗 CPU 时间,而没有真正做有用的工作。 - 死锁风险:如果不释放
Mutex
就进行等待,会导致其他 goroutine 无法获取Mutex
来修改共享状态,从而造成死锁。例如,在生产者 - 消费者模型中,如果生产者发现缓冲区满时不释放Mutex
就等待,消费者永远无法获取Mutex
来消费数据,反之亦然,最终导致死锁。