MST

星途 面试题库

面试题:如何在Go中通过调整Mutex锁提升性能并兼顾公平性

假设你正在开发一个高并发的Go应用,其中大量的goroutine竞争同一个Mutex锁。在这种场景下,你会采取哪些策略来提升性能,同时又能尽量保证公平性?请详细说明你的思路以及可能涉及到的代码实现。
50.1万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

提升性能并保证公平性的策略

  1. 分段锁策略
    • 思路:将需要保护的资源按照一定规则进行分段,每个分段使用一个独立的Mutex。这样不同的goroutine可以同时访问不同分段的资源,减少锁竞争。例如,假设有一个大的数组资源,我们可以按数组下标范围进行分段。
    • 代码示例
package main

import (
    "fmt"
    "sync"
)

const numSegments = 10

var segments = make([]sync.Mutex, numSegments)

func getSegmentIndex(key int) int {
    return key % numSegments
}

func accessResource(key, value int) {
    index := getSegmentIndex(key)
    segments[index].Lock()
    defer segments[index].Unlock()
    // 这里进行对资源的操作,比如假设资源是一个全局数组
    // globalArray[key] = value
    fmt.Printf("Accessed resource with key %d and value %d\n", key, value)
}
  1. 读写锁优化(适用于读多写少场景)
    • 思路:如果对共享资源的操作主要是读操作,使用sync.RWMutex。多个读操作可以同时进行,只有写操作需要独占锁,这样能大大提高并发性能。读操作时不影响其他读操作,只有写操作时才会阻塞读和其他写操作。
    • 代码示例
package main

import (
    "fmt"
    "sync"
)

var rwMutex sync.RWMutex
var sharedData int

func readData() {
    rwMutex.RLock()
    defer rwMutex.RUnlock()
    fmt.Printf("Read data: %d\n", sharedData)
}

func writeData(newData int) {
    rwMutex.Lock()
    defer rwMutex.Unlock()
    sharedData = newData
    fmt.Printf("Wrote data: %d\n", sharedData)
}
  1. 使用公平锁
    • 思路:Go 1.9 引入了公平模式的Mutex。在公平模式下,Mutex 会按照请求的顺序来授予锁,等待时间最长的 goroutine 会优先获得锁,从而保证公平性。在高竞争场景下,公平锁虽然会有一定的性能开销,但能保证所有请求都有机会获取锁。
    • 代码示例
package main

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

var mu sync.Mutex

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    for i := 0; i < 3; i++ {
        mu.Lock()
        fmt.Printf("Worker %d acquired lock\n", id)
        time.Sleep(100 * time.Millisecond)
        fmt.Printf("Worker %d released lock\n", id)
        mu.Unlock()
    }
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }
    wg.Wait()
}
  • 在上述代码基础上,如果要使用公平锁,可以在Mutex定义后添加如下代码:
var mu sync.Mutex
func init() {
    mu = sync.Mutex{}
    mu.TryLock() // 触发公平模式初始化
    mu.Unlock()
}
  1. 减少锁的持有时间
    • 思路:尽量将不需要锁保护的操作移出临界区,缩短锁的持有时间,从而减少其他goroutine等待锁的时间,提高并发性能。
    • 代码示例
package main

import (
    "fmt"
    "sync"
)

var mu sync.Mutex
var data int

func process() {
    // 计算一些临时值,这些操作不需要锁保护
    temp := 10 + 20
    mu.Lock()
    data = temp
    fmt.Printf("Set data to %d\n", data)
    mu.Unlock()
    // 后续操作如果不需要锁保护,可以继续在这里进行
    result := data * 2
    fmt.Printf("Calculated result: %d\n", result)
}