MST

星途 面试题库

面试题:Go语言sync.Cond的Wait方法实现机制及应用场景

详细说明Go语言中sync.Cond的Wait方法的实现机制,包括它是如何释放和重新获取关联的Mutex。结合实际应用场景,举例说明在哪些情况下,使用sync.Cond的Wait方法能够有效地解决并发编程中的同步问题,并且解释如果不使用Wait方法而采用其他方式可能会带来哪些问题。
25.1万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

sync.Cond的Wait方法实现机制

  1. 释放Mutex:当调用 sync.CondWait 方法时,它首先会释放与该 Cond 关联的 Mutex。这是通过调用 runtime_notifyListWait 函数来实现的,该函数会将当前 goroutine 加入到等待队列中,并在加入队列前释放 Mutex。这一步非常关键,因为如果不释放 Mutex,其他 goroutine 将无法获取 Mutex 进而修改共享状态,导致死锁。
  2. 等待唤醒:释放 Mutex 后,当前 goroutine 会进入睡眠状态,等待被其他 goroutine 通过 CondSignalBroadcast 方法唤醒。
  3. 重新获取Mutex:当被唤醒时,Wait 方法会重新获取之前释放的 Mutex。在重新获取 Mutex 成功后,Wait 方法才会返回,使得当前 goroutine 可以安全地访问共享资源。

实际应用场景

  1. 生产者 - 消费者模型:在生产者 - 消费者模型中,当缓冲区已满时,生产者需要等待,直到消费者从缓冲区中取出数据,使缓冲区有空间。同样,当缓冲区为空时,消费者需要等待,直到生产者向缓冲区中放入数据。
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
}

在上述代码中,ProduceConsume 方法使用 sync.CondWait 方法来处理缓冲区满和缓冲区空的情况,确保了生产者和消费者之间的正确同步。

不使用Wait方法的问题

  1. 忙等待(Busy Waiting):如果不使用 Wait 方法,生产者和消费者可能会采用忙等待的方式来检查缓冲区的状态。例如,生产者可能会不断循环检查缓冲区是否有空间,消费者可能会不断循环检查缓冲区是否有数据。这会浪费大量的 CPU 资源,因为在等待期间,goroutine 一直在消耗 CPU 时间,而没有真正做有用的工作。
  2. 死锁风险:如果不释放 Mutex 就进行等待,会导致其他 goroutine 无法获取 Mutex 来修改共享状态,从而造成死锁。例如,在生产者 - 消费者模型中,如果生产者发现缓冲区满时不释放 Mutex 就等待,消费者永远无法获取 Mutex 来消费数据,反之亦然,最终导致死锁。