面试题答案
一键面试1. sync.Cond
与互斥锁配合实现唤醒机制
在Go语言中,sync.Cond
依赖一个互斥锁(通常是 sync.Mutex
或 sync.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 来演示整个过程。