go从零单排之RWMutex

0 阅读3分钟

Go sync.RWMutex 读写锁 原子实现


一、RWMutex 到底是什么?

一句话:

RWMutex = 读多写少场景的高性能锁 → 读读不互斥、读写互斥、写写互斥

核心

  • 读 ↔ 读:不阻塞,共享执行(多人可以同时读)
  • 读 ↔ 写:互斥,必须等对方结束
  • 写 ↔ 写:完全互斥,只能一个写

设计思想

读锁用计数器 + 写锁用排队 + 全部基于原子操作

读多的时候几乎无锁,写的时候严格互斥。


二、RWMutex 核心结构体

文件:sync/rwmutex.go

1. RWMutex 结构体

type RWMutex struct {
        w           Mutex        // 写锁:互斥锁(保证同一时间只有一个写)
        writerSem   uint32       // 写等待信号量:写操作等待读完成时休眠
        readerSem   uint32       // 读等待信号量:读等待写完成时休眠
        // 读计数器(原子核心!)
        // 含义:
        //   > 0 :持有读锁的 goroutine 数量
        //   < 0 :有写锁在占用(-写等待者数量)
        readerCount atomic.Int32
        // 等待完成的读操作数量(原子)
        readerWait  atomic.Int32
}

2. 核心常量

// 最大读数量:2^30
const rwmutexMaxReaders = 1 << 30

三、核心原理

  1. 读锁:readerCount++(原子加)
  1. 写锁:readerCount -= rwmutexMaxReaders(把读计数变成负数,标记 “写锁占用”)
  1. 解锁:原子恢复计数 + 唤醒等待者

四、完整源码 + 逐行详细注解

1. RLock () 读加锁(无阻塞、原子操作)

// RLock 加读锁
func (rw *RWMutex) RLock() {
        // 原子 +1
        // 如果结果 < 0 → 说明有写锁在占用,读必须等待
        if rw.readerCount.Add(1) < 0 {
                // 有写锁,休眠等待写释放
                runtime_Semacquire(&rw.readerSem)
        }
}

2. RUnlock () 解读锁

// RUnlock 解读锁
func (rw *RWMutex) RUnlock() {
        // 原子 -1
        if r := rw.readerCount.Add(-1); r < 0 {
                // 读计数 <0 → 有写锁在等待最后一个读结束
                rw.rUnlockSlow(r)
        }
}
// 慢路径:唤醒写锁
func (rw *RWMutex) rUnlockSlow(r int32) {
        // 等待的读操作 -1
        if rw.readerWait.Add(-1) == 0 {
                // 最后一个读完成 → 唤醒写锁
                runtime_Semrelease(&rw.writerSem, false, 0)
        }
}

3. Lock () 写加锁(最核心)

// Lock 加写锁
func (rw *RWMutex) Lock() {
        // 第一步:获取互斥写锁(保证只有一个写)
        rw.w.Lock()
        // 第二步:原子把 readerCount 减去最大值 → 变成负数
        // 负数 = 标记“写锁占用”,禁止新读进来
        r := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders
        // 第三步:如果还有读没释放,等待所有读结束
        if r != 0 && rw.readerWait.Add(r) != 0 {
                // 休眠,等待最后一个读唤醒
                runtime_Semacquire(&rw.writerSem)
        }
}

4. Unlock () 写解锁

// Unlock 解写锁
func (rw *RWMutex) Unlock() {
        // 第一步:恢复 readerCount 为正数
        r := rw.readerCount.Add(rwmutexMaxReaders)
        // 第二步:唤醒所有等待的读锁
        for i := 0; i < int(r); i++ {
                runtime_Semrelease(&rw.readerSem, false, 0)
        }
        // 第三步:释放写互斥锁
        rw.w.Unlock()
}

五、RWMutex 流程图

1. 读锁 RLock () 流程

image.png

2. 写锁 Lock () 流程

image.png

3. 写解锁 Unlock () 流程

image.png


六、总结

1. RWMutex 完全基于 原子操作

  • 读计数:原子增减
  • 写标记:负数表示写锁占用
  • 无系统调用,用户态实现,极快

2. 三大核心规则

  • 读读共享(不阻塞)
  • 读写互斥
  • 写写互斥

3. 写锁如何 “插队”?

把 readerCount 变成负数,新读全部阻塞排队。

4. 读锁如何不阻塞?

只做原子 + 1,无锁、无等待、极快。


七、面试

1. RWMutex 为什么快?

读读不互斥,全部原子操作,读多场景性能远超 Mutex。

2. 写锁是怎么阻止新读的?

把 readerCount 变成负数,新读 RLock () 发现 <0 就会休眠。

3. 写锁等待什么?

等待所有已存在的读锁全部释放。

4. RWMutex 适合什么场景?

读多写少:配置、缓存、元数据。

5. RWMutex 底层依赖什么?

atomic 原子操作 + sema 信号量休眠唤醒 + 内部互斥锁。