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
三、核心原理
- 读锁:readerCount++(原子加)
- 写锁:readerCount -= rwmutexMaxReaders(把读计数变成负数,标记 “写锁占用”)
- 解锁:原子恢复计数 + 唤醒等待者
四、完整源码 + 逐行详细注解
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 () 流程
2. 写锁 Lock () 流程
3. 写解锁 Unlock () 流程
六、总结
1. RWMutex 完全基于 原子操作
- 读计数:原子增减
- 写标记:负数表示写锁占用
- 无系统调用,用户态实现,极快
2. 三大核心规则
- 读读共享(不阻塞)
- 读写互斥
- 写写互斥
3. 写锁如何 “插队”?
把 readerCount 变成负数,新读全部阻塞排队。
4. 读锁如何不阻塞?
只做原子 + 1,无锁、无等待、极快。
七、面试
1. RWMutex 为什么快?
读读不互斥,全部原子操作,读多场景性能远超 Mutex。
2. 写锁是怎么阻止新读的?
把 readerCount 变成负数,新读 RLock () 发现 <0 就会休眠。
3. 写锁等待什么?
等待所有已存在的读锁全部释放。
4. RWMutex 适合什么场景?
读多写少:配置、缓存、元数据。
5. RWMutex 底层依赖什么?
atomic 原子操作 + sema 信号量休眠唤醒 + 内部互斥锁。