MST

星途 面试题库

面试题:Go语言sync包中条件变量与互斥锁的协同优化

在高并发场景下,sync包中的条件变量(Cond)经常与互斥锁(Mutex)协同使用。请深入分析这种协同工作的机制,并举例说明如何通过优化条件变量与互斥锁的使用,来提升系统性能和资源利用率。同时,请指出在这种协同使用过程中容易出现的死锁场景及避免方法。
40.6万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

协同工作机制

  1. Mutex的作用:Mutex用于保护共享资源,确保在同一时间只有一个goroutine能够访问共享资源。在使用Cond时,通常先获取Mutex,以保证对共享数据的操作是线程安全的。
  2. Cond的作用:Cond依赖于Mutex,它提供了一种线程间同步的方式。当某个条件不满足时,goroutine可以通过Cond的Wait方法进入等待状态,同时释放Mutex,使得其他goroutine可以修改共享资源。当条件满足时,通过SignalBroadcast方法唤醒等待的goroutine,被唤醒的goroutine会重新获取Mutex,然后继续执行。

优化示例

假设我们有一个生产者 - 消费者模型,生产者向共享队列中添加数据,消费者从队列中取出数据。

package main

import (
    "fmt"
    "sync"
)

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(v int) {
    q.mu.Lock()
    defer q.mu.Unlock()
    q.data = append(q.data, v)
    q.cond.Broadcast()
}

func (q *Queue) Dequeue() int {
    q.mu.Lock()
    for len(q.data) == 0 {
        q.cond.Wait()
    }
    v := q.data[0]
    q.data = q.data[1:]
    q.mu.Unlock()
    return v
}

在这个例子中,Enqueue方法添加数据后调用Broadcast唤醒所有等待的消费者,Dequeue方法在队列为空时调用Wait等待,当被唤醒且获取到锁后继续从队列取数据。这样可以避免不必要的等待和空转,提升系统性能和资源利用率。

死锁场景及避免方法

  1. 死锁场景
    • 忘记解锁Mutex:在调用Wait方法前没有释放Mutex,或者在SignalBroadcast后没有正确处理Mutex,可能导致死锁。例如:
func wrongUsage() {
    var mu sync.Mutex
    cond := sync.NewCond(&mu)
    mu.Lock()
    cond.Wait() // 这里会导致死锁,因为Wait方法需要先释放锁
    mu.Unlock()
}
- **重复获取锁**:如果在已经持有Mutex的情况下再次获取Mutex,也会导致死锁。例如:
func doubleLock() {
    var mu sync.Mutex
    mu.Lock()
    mu.Lock() // 这里会导致死锁
    mu.Unlock()
    mu.Unlock()
}
  1. 避免方法
    • 严格遵循使用规范:在调用Wait方法前一定要先获取Mutex,并且Wait方法会自动释放Mutex并挂起当前goroutine,被唤醒后会重新获取Mutex。在使用SignalBroadcast时,要确保Mutex的状态是正确的,通常在持有Mutex的情况下调用这些方法。
    • 代码审查:通过代码审查,仔细检查锁的获取和释放逻辑,确保没有重复获取锁或忘记释放锁的情况。