MST

星途 面试题库

面试题:Go并发数据结构中读写锁与GoMutex的选择权衡

在Go的并发编程中,有时我们可以选择读写锁(`sync.RWMutex`)或GoMutex(`sync.Mutex`)来保护数据结构。请分析在何种场景下应该优先选择读写锁,何种场景下应该优先选择GoMutex,并举例说明在一个复杂的并发数据结构(如带有缓存的数据库查询结果集)中,如何根据读写操作的频率和特性来选择合适的锁机制。
12.7万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

读写锁(sync.RWMutex)适用场景

  1. 读多写少场景:当对数据结构的读操作频率远高于写操作时,优先选择读写锁。因为读写锁允许多个读操作同时进行,不会相互阻塞,只有写操作会独占资源,这样可以大大提高并发读的效率。例如,一个提供股票实时行情的系统,大量用户会频繁读取股票价格,但只有少数情况下会更新股票价格。

GoMutex(sync.Mutex)适用场景

  1. 读写操作频率相近或写多场景:如果读写操作频率相近,或者写操作频率高于读操作,使用普通的互斥锁(sync.Mutex)更为合适。因为读写锁在写操作时会阻塞所有读操作,若写操作频繁,使用读写锁反而会因频繁的锁切换降低性能。例如,一个在线投票系统,投票操作(写)和查看当前票数(读)频率相近,此时普通互斥锁能更好地保证数据一致性和性能。

在复杂并发数据结构(带有缓存的数据库查询结果集)中的应用

  1. 读多写少: 假设缓存的数据库查询结果集主要用于快速读取,只有在数据库数据更新时才会更新缓存。比如一个新闻网站的文章缓存,用户大量读取文章内容,但文章更新频率较低。这种情况下,使用读写锁。
package main

import (
    "fmt"
    "sync"
)

type CachedResultSet struct {
    data   map[string]interface{}
    rwLock sync.RWMutex
}

func (c *CachedResultSet) Read(key string) (interface{}, bool) {
    c.rwLock.RLock()
    defer c.rwLock.RUnlock()
    value, exists := c.data[key]
    return value, exists
}

func (c *CachedResultSet) Write(key string, value interface{}) {
    c.rwLock.Lock()
    defer c.rwLock.Unlock()
    if c.data == nil {
        c.data = make(map[string]interface{})
    }
    c.data[key] = value
}
  1. 读写相近或写多: 若缓存的更新较为频繁,比如实时监控系统中缓存的传感器数据,传感器数据更新频率高且监控端也会频繁读取。这时用普通互斥锁。
package main

import (
    "fmt"
    "sync"
)

type CachedResultSet struct {
    data   map[string]interface{}
    lock   sync.Mutex
}

func (c *CachedResultSet) Read(key string) (interface{}, bool) {
    c.lock.Lock()
    defer c.lock.Unlock()
    value, exists := c.data[key]
    return value, exists
}

func (c *CachedResultSet) Write(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
}