面试题答案
一键面试Go语言RWMutex锁底层实现原理
- 数据结构:
RWMutex
结构体定义在src/sync/rwmutex.go
中,其核心字段如下:
type RWMutex struct { w Mutex // 用于写操作的互斥锁 writerSem uint32 // 写信号量,用于阻塞写操作 readerSem uint32 // 读信号量,用于阻塞读操作 readerCount int32 // 记录当前活跃的读操作数量 readerWait int32 // 记录等待写操作完成的读操作数量 }
- 读操作(
RLock
):- 首先,
readerCount
加1。如果readerCount
变为负数,说明有写操作正在进行或者等待,此时当前读操作需要阻塞。 - 通过
atomic.AddInt32(&rw.readerCount, 1)
增加读操作计数。 - 如果
readerCount
为负,调用runtime_SemacquireMutex(&rw.readerSem, false, 0)
获取读信号量,进入等待状态,直到写操作完成并释放读信号量。
- 首先,
- 读解锁(
RUnlock
):readerCount
减1。如果readerCount
变为0,说明没有活跃的读操作了,如果有写操作等待(readerWait
不为0),则通过runtime_Semrelease(&rw.writerSem, false, 1)
释放写信号量,唤醒一个等待的写操作。- 通过
atomic.AddInt32(&rw.readerCount, -1)
减少读操作计数。
- 写操作(
Lock
):- 首先获取
w
互斥锁,这一步保证同一时间只有一个写操作可以进入临界区。 - 然后记录当前活跃的读操作数量
readerCount
到readerWait
,并将readerCount
设为负数,这样后续的读操作会被阻塞。 - 接着循环等待,直到
readerCount
变为0,即所有读操作都完成,此时调用runtime_SemacquireMutex(&rw.writerSem, true, 0)
获取写信号量。
- 首先获取
- 写解锁(
Unlock
):- 释放
w
互斥锁。 - 将
readerCount
恢复为原来的值(正数),同时通过runtime_Semrelease(&rw.readerSem, true, 0)
释放读信号量,唤醒所有等待的读操作。
- 释放
极端读多写少场景优化设计与实现
- 设计思路:
- 采用读写分离的思想,对于读操作,尽量避免与写操作的竞争。可以考虑使用缓存机制,让读操作优先从缓存获取数据,减少对共享资源的直接读取,从而降低读锁的竞争。
- 对于写操作,采用批量处理的方式,减少写操作的频率。例如,在内存中维护一个写操作队列,当队列达到一定长度或者经过一定时间,再进行一次批量写操作。
- 实现示例:
package main import ( "container/list" "fmt" "sync" "time" ) type RWCache struct { cache map[string]interface{} rwMutex sync.RWMutex writeQueue *list.List writeTimer *time.Timer mutex sync.Mutex } func NewRWCache() *RWCache { rwc := &RWCache{ cache: make(map[string]interface{}), writeQueue: list.New(), writeTimer: time.NewTimer(5 * time.Second), } go rwc.flushQueue() return rwc } func (rwc *RWCache) Get(key string) (interface{}, bool) { rwc.rwMutex.RLock() defer rwc.rwMutex.RUnlock() value, exists := rwc.cache[key] return value, exists } func (rwc *RWCache) Set(key string, value interface{}) { rwc.mutex.Lock() rwc.writeQueue.PushBack(&writeOp{key, value}) if rwc.writeQueue.Len() >= 10 { rwc.flushQueue() } rwc.mutex.Unlock() } type writeOp struct { key string value interface{} } func (rwc *RWCache) flushQueue() { for { select { case <-rwc.writeTimer.C: rwc.mutex.Lock() for e := rwc.writeQueue.Front(); e != nil; e = e.Next() { op := e.Value.(*writeOp) rwc.rwMutex.Lock() rwc.cache[op.key] = op.value rwc.rwMutex.Unlock() } rwc.writeQueue.Init() rwc.writeTimer.Reset(5 * time.Second) rwc.mutex.Unlock() } } }
- 在上述代码中:
RWCache
结构体包含一个缓存cache
,一个读写锁rwMutex
,一个写操作队列writeQueue
和一个定时器writeTimer
。Get
方法使用读锁来读取缓存数据。Set
方法将写操作加入队列,当队列长度达到10或者定时器到期时,进行批量写操作。flushQueue
方法在后台运行,负责处理写队列中的操作。这样在极端读多写少场景下,可以有效减少写操作对读操作的影响,提高整体性能。
- 在上述代码中: