MST

星途 面试题库

面试题:Go高并发场景下互斥锁与读写锁的选择与性能分析

在一个高并发的Go程序中,存在大量读操作和少量写操作。你可以选择使用互斥锁(`sync.Mutex`)或者读写锁(`sync.RWMutex`)。请详细阐述在这种场景下两者的优缺点,以及如何通过性能测试来证明你的选择是最优的,并给出简单的性能测试代码示例。
36.1万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

互斥锁(sync.Mutex)

  1. 优点
    • 实现简单,逻辑清晰。无论读操作还是写操作,都通过加锁来保证同一时间只有一个 goroutine 能访问共享资源,能很好地保证数据一致性。
  2. 缺点
    • 在高并发读多写少的场景下,性能较差。因为读操作也会被锁限制,即使读操作之间不会产生数据竞争,也不能并行执行,这会导致大量读操作等待锁释放,降低了程序的并发性能。

读写锁(sync.RWMutex)

  1. 优点
    • 在读多写少的场景下性能优势明显。读操作可以并发执行,只要没有写操作在进行,多个读操作可以同时获取读锁,大大提高了读操作的并发度。写操作则独占锁,保证数据一致性。
  2. 缺点
    • 实现相对复杂,需要考虑读锁和写锁的不同使用场景。并且写操作时会阻塞所有读操作,当写操作频繁时,可能会导致读操作长时间等待。

性能测试

  1. 测试互斥锁性能
package main

import (
    "fmt"
    "sync"
    "time"
)

var mu sync.Mutex
var data int

func readMutex(wg *sync.WaitGroup) {
    defer wg.Done()
    mu.Lock()
    _ = data
    mu.Unlock()
}

func writeMutex(wg *sync.WaitGroup) {
    defer wg.Done()
    mu.Lock()
    data++
    mu.Unlock()
}

func main() {
    var wg sync.WaitGroup
    start := time.Now()
    for i := 0; i < 1000; i++ {
        if i%100 == 0 {
            wg.Add(1)
            go writeMutex(&wg)
        } else {
            wg.Add(1)
            go readMutex(&wg)
        }
    }
    wg.Wait()
    elapsed := time.Since(start)
    fmt.Printf("互斥锁总耗时: %s\n", elapsed)
}
  1. 测试读写锁性能
package main

import (
    "fmt"
    "sync"
    "time"
)

var rwmu sync.RWMutex
var data2 int

func readRWMutex(wg *sync.WaitGroup) {
    defer wg.Done()
    rwmu.RLock()
    _ = data2
    rwmu.RUnlock()
}

func writeRWMutex(wg *sync.WaitGroup) {
    defer wg.Done()
    rwmu.Lock()
    data2++
    rwmu.Unlock()
}

func main() {
    var wg sync.WaitGroup
    start := time.Now()
    for i := 0; i < 1000; i++ {
        if i%100 == 0 {
            wg.Add(1)
            go writeRWMutex(&wg)
        } else {
            wg.Add(1)
            go readRWMutex(&wg)
        }
    }
    wg.Wait()
    elapsed := time.Since(start)
    fmt.Printf("读写锁总耗时: %s\n", elapsed)
}

通过上述代码分别测试互斥锁和读写锁在相同读多写少场景下的总耗时,对比两者耗时长短,若读写锁耗时明显短于互斥锁,则证明在该场景下读写锁是更优选择。