RWMutex 探究

golang 中 mutex 是标准的锁工具,在此使用 RWMutex 为例,介绍实现机制,本文中某些知识点在之前描述Mutex 的文章中提及,基于golang 1.24.5代码。

RWMutex 保证同一时刻可以存在多个读请求或一个写请求。

RWMutex 包含了Mutex 用于加解锁本结构体之外,还维护了readerCount表示等待中的读操作(goroutine)数量,readerWait 表示写操作需要等待的读操作数量。

当有读请求加锁时,若readerCount为负数,则先阻塞,等待唤醒;若大于等于0,则将readerCount加1,

当读请求解锁时,将readerCountreaderWait减一。唤醒阻塞的写请求

当有写请求加锁时,将原先readerCount反转为负数。若readerCount 不为0且readerWait不为0,则阻塞等待唤醒。

当写请求解锁时,将readerCount从负数反转为正数,并唤醒阻塞的读请求

使用方法

为了实现锁,golang 中通常使用,RWMutex 使用方法大致如下,在对象中包含*sync.RWMutex

type Config struct {
    configMutex *sync.RWMutex
    config      clusterConfigurer
    state       map[string]*State
}

当代码中可能存在并发修改对象中值的情况,使用Lock 方法加锁后修改,然后再解锁。

c.configMutex.Lock()
c.state[string(UID)] = state
c.configMutex.Unlock()

若需要读数据并且希望不被写操作影响,可以使用RLock() 方法

c.configMutex.RLock()
state, ok := c.state[string(UID)]
c.configMutex.RUnlock()

RWMutex实现

RWMutex 的实现中包含了一个Mutex,其中writerSem,readerSem 分别代表读写完成的信号量(后文中会解释)。readerCount表示等待中的读操作(goroutine)数量,readerWait 表示写操作需要等待的读操作数量。

type RWMutex struct {
    w           Mutex        // held if there are pending writers
    writerSem   uint32       // semaphore for writers to wait for completing readers
    readerSem   uint32       // semaphore for readers to wait for completing writers
    readerCount atomic.Int32 // number of pending readers
    readerWait  atomic.Int32 // number of departing readers
}

lock 方法

在 RWMutex 的 lock 方法中,race.Enabled 里的逻辑用于调试跟踪锁的访问,检测数据竞争问题。w.Lock() 之后首先获取当前存在的 reader 数量。并将readerCount 置为负数,r != 0 && rw.readerWait.Add(r) != 0 判断了当前存在的读操作的数量。若读操作数量大于0 则通过runtime_SemacquireRWMutex 方法与m 解绑,阻塞并等待&rw.writerSem信号量。

// Lock locks rw for writing.
// If the lock is already locked for reading or writing,
// Lock blocks until the lock is available.
func (rw *RWMutex) Lock() {
...
    // First, resolve competition with other writers.
    rw.w.Lock()
    // Announce to readers there is a pending writer.
    r := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders
    // Wait for active readers.
    if r != 0 && rw.readerWait.Add(r) != 0 {
       runtime_SemacquireRWMutex(&rw.writerSem, false, 0)
    }
...
}

在此讨论一下为什么需要判断rw.readerWait.Add(r) != 0,我们需要从RUnlock() 方法入手

func (rw *RWMutex) RUnlock() {
    if r := rw.readerCount.Add(-1); r < 0 {
       // Outlined slow-path to allow the fast-path to be inlined
       rw.rUnlockSlow(r)
    }
}

可以看到如果存当前存在写操作(readerCount<0 != 0)则进入rUnlockSlow

func (rw *RWMutex) rUnlockSlow(r int32) {
    if rw.readerWait.Add(-1) == 0 {
       // The last reader unblocks the writer.
       runtime_Semrelease(&rw.writerSem, false, 1)
    }
}

将 readerWait -1 如果这时候 readerWait 为 0 则唤醒写信号量,这时候卡在runtime_SemacquireRWMutex(&rw.writerSem, false, 0) 处的程序将向后执行。

因此可能出现极端情况,当写操作准备等待读操作时,若读操作恰好已全部完成(导致 readerWait 累加后为 0),则写操作无需阻塞,可直接继续执行。

//r = 5

r := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders

//读操作完成 readerWait = -5 ,r != 0 但 rw.readerWait.Add(r) = 0

if r != 0 && rw.readerWait.Add(r) != 0 {

}

因此可以概况为:

  1. 先通过互斥锁与其他写操作竞争;
  2. 标记 “有写等待”,阻止新读操作进入;
  3. 等待所有当前活跃的读操作完成;
  4. 最终获得写锁,确保独占访问。

RLock 方法

在RLock 方法中,将readerCount 加1,若readerCount<0(有写操作),则等待readerSem读信号量,若无写操作,则直接获取。

func (rw *RWMutex) RLock() {
...
    if rw.readerCount.Add(1) < 0 {
       // A writer is pending, wait for it.
       runtime_SemacquireRWMutexR(&rw.readerSem, false, 0)
    }
...
}

RUnlock方法

RUnlock方法中,将readerCount 减1,若这时readerCount <0 ,说明有写操作阻塞,需要进入rw.rUnlockSlow(r)

// RUnlock undoes a single [RWMutex.RLock] call;
// it does not affect other simultaneous readers.
// It is a run-time error if rw is not locked for reading
// on entry to RUnlock.
func (rw *RWMutex) RUnlock() {
...
    if r := rw.readerCount.Add(-1); r < 0 {
       // Outlined slow-path to allow the fast-path to be inlined
       rw.rUnlockSlow(r)
    }
...
}

将rw.readerWait 减一,直到rw.readerWait 为0 的时候通过rw.writerSem信号量,唤醒正在等待的写操作。

func (rw *RWMutex) rUnlockSlow(r int32) {
...
    // A writer is pending.
    if rw.readerWait.Add(-1) == 0 {
       // The last reader unblocks the writer.
       runtime_Semrelease(&rw.writerSem, false, 1)
    }
}

Unlock方法

在unlock 方法中,将readerCount 从负数更新为正数,这时readerCount 是正在阻塞的读操作,通过readerSem 读信号量将其唤醒。

// Unlock unlocks rw for writing. It is a run-time error if rw is
// not locked for writing on entry to Unlock.
//
// As with Mutexes, a locked [RWMutex] is not associated with a particular
// goroutine. One goroutine may [RWMutex.RLock] ([RWMutex.Lock]) a RWMutex and then
// arrange for another goroutine to [RWMutex.RUnlock] ([RWMutex.Unlock]) it.
func (rw *RWMutex) Unlock() {
    if race.Enabled {
       race.Read(unsafe.Pointer(&rw.w))
       race.Release(unsafe.Pointer(&rw.readerSem))
       race.Disable()
    }

    // Announce to readers there is no active writer.
    r := rw.readerCount.Add(rwmutexMaxReaders)
    if r >= rwmutexMaxReaders {
       race.Enable()
       fatal("sync: Unlock of unlocked RWMutex")
    }
    // Unblock blocked readers, if any.
    for i := 0; i < int(r); i++ {
       runtime_Semrelease(&rw.readerSem, false, 0)
    }
    // Allow other writers to proceed.
    rw.w.Unlock()
    if race.Enabled {
       race.Enable()
    }
}
updatedupdated2025-10-172025-10-17