MST

星途 面试题库

面试题:Go中Goroutine并发安全之读写锁应用场景

假设你正在开发一个缓存系统,有多个Goroutine会频繁读取缓存数据,偶尔会有Goroutine更新缓存数据。请解释为什么使用读写锁(`sync.RWMutex`)比单纯使用互斥锁(`sync.Mutex`)更适合这个场景,并给出具体实现代码,展示如何使用读写锁保证缓存操作的并发安全。
17.3万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试
  1. 原因解释
    • 互斥锁sync.Mutex 是一种简单的锁机制,同一时间只允许一个Goroutine获取锁进行操作。在缓存系统中,频繁的读取操作意味着大量的Goroutine会竞争锁,如果使用互斥锁,每次读取都需要获取锁,这会导致读操作的并发性能严重下降,因为读操作本身并不会修改数据,多个读操作同时进行不会产生数据冲突。
    • 读写锁sync.RWMutex 区分了读锁和写锁。多个Goroutine可以同时获取读锁进行读取操作,只有当有Goroutine需要写入数据时,才需要获取写锁,并且获取写锁时会阻止其他所有读和写操作。这就大大提高了读操作的并发性能,非常适合缓存系统这种读多写少的场景。
  2. 具体实现代码
package main

import (
    "fmt"
    "sync"
)

type Cache struct {
    data map[string]interface{}
    lock sync.RWMutex
}

// Get 获取缓存数据
func (c *Cache) Get(key string) (interface{}, bool) {
    c.lock.RLock()
    defer c.lock.RUnlock()
    value, exists := c.data[key]
    return value, exists
}

// Set 设置缓存数据
func (c *Cache) Set(key string, value interface{}) {
    c.lock.Lock()
    defer c.lock.Unlock()
    if c.data == nil {
        c.data = make(map[string]interface{})
    }
    c.data[key] = value
}

func main() {
    cache := Cache{}
    var wg sync.WaitGroup

    // 模拟多个读操作
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            key := fmt.Sprintf("key%d", id)
            value, exists := cache.Get(key)
            if exists {
                fmt.Printf("Goroutine %d read value: %v\n", id, value)
            } else {
                fmt.Printf("Goroutine %d key not found\n", id)
            }
        }(i)
    }

    // 模拟一个写操作
    wg.Add(1)
    go func() {
        defer wg.Done()
        cache.Set("key1", "value1")
        fmt.Println("Write operation completed")
    }()

    wg.Wait()
}

在上述代码中:

  • Cache 结构体包含一个 map 用于存储缓存数据,以及一个 sync.RWMutex 用于保证并发安全。
  • Get 方法使用 RLock 读锁,允许多个Goroutine同时读取缓存数据。
  • Set 方法使用 Lock 写锁,在写入数据时会阻止其他读和写操作,确保数据的一致性。