MST

星途 面试题库

面试题:Go互斥锁与读写锁在复杂并发场景中的选择与应用

假设有一个Go程序,其中有多个协程对一个数据结构进行读写操作。读操作非常频繁,写操作相对较少,但写操作时不允许有读操作同时进行。请设计一个使用互斥锁(`sync.Mutex`)和读写锁(`sync.RWMutex`)结合的方案来高效地处理这种情况,并详细解释每个锁在不同场景下的使用逻辑和切换时机。
18.6万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试
  1. 方案设计
    • 对于读操作频繁,写操作较少的场景,优先使用读写锁(sync.RWMutex)来提升读操作的并发性能。当进行写操作时,因为不允许有读操作同时进行,所以需要使用互斥锁(sync.Mutex)来保证写操作的原子性。
    • 以下是示例代码:
package main

import (
    "fmt"
    "sync"
)

type Data struct {
    mu    sync.Mutex
    rwmu  sync.RWMutex
    value int
}

// Read 方法用于读操作
func (d *Data) Read() int {
    d.rwmu.RLock()
    defer d.rwmu.RUnlock()
    return d.value
}

// Write 方法用于写操作
func (d *Data) Write(newValue int) {
    d.mu.Lock()
    defer d.mu.Unlock()

    d.rwmu.Lock()
    defer d.rwmu.Unlock()

    d.value = newValue
}
  1. 锁的使用逻辑和切换时机
    • 读操作逻辑
      • Read方法中,使用d.rwmu.RLock()获取读锁。读锁允许多个读操作同时进行,因为读操作不会修改数据,所以多个读操作之间不会产生数据竞争。当读操作完成后,使用d.rwmu.RUnlock()释放读锁。
    • 写操作逻辑
      • 首先使用d.mu.Lock()获取互斥锁。这一步的目的是确保在写操作开始前,没有其他写操作正在进行。因为写操作需要保证原子性,不允许其他写操作同时修改数据。
      • 然后使用d.rwmu.Lock()获取写锁。这一步是为了确保在写操作进行时,没有读操作同时进行。虽然读写锁本身可以在写操作时阻止读操作,但为了更严谨地保证写操作的原子性,先获取互斥锁再获取写锁。
      • 当写操作完成后,先使用d.rwmu.Unlock()释放写锁,再使用d.mu.Unlock()释放互斥锁。

通过这种方式,利用读写锁提升读操作的并发性能,同时使用互斥锁保证写操作的原子性以及写操作和读操作之间的互斥性。