Go 源码剖析:sync.RWMutex 读写互斥锁原理 📅 2026/8/2 9:09:38 在并发场景中很多业务具备读多写少的特征大量协程并发读取共享资源写操作相对稀少。 普通sync.Mutex是排他锁不管读还是写同一时间只允许一个协程持有锁并发读场景性能很差。sync.RWMutex读写锁应运而生读与读共享、读写互斥、写写互斥大幅提升读密集场景并发能力。本文基于 Go 标准库sync.RWMutex底层机制拆解结构体、四大接口、锁竞争逻辑同时解答经典问题写锁如何避免饥饿。一、RWMutex 基础特性核心规则先行✅ 读锁不阻塞读锁多个协程可以同时获取读锁并发读取❌ 读锁阻塞写锁、写锁阻塞读锁❌ 写锁阻塞写锁同一时刻只能存在一个写协程对外暴露 4 个核心 APIRLock()获取读锁RUnlock()释放读锁Lock()获取写锁Unlock()释放写锁二、RWMutex 结构体核心字段type RWMutex struct { w Mutex // 互斥锁用于隔离多个写操作 writerSem uint32 // 写协程信号量唤醒等待的写协程 readerSem uint32 // 读协程信号量唤醒等待的读协程 readerCount int32 // 读计数器标记当前读协程数量 readerWait int32 // 用于解决写锁饥饿问题 }重点字段作用总览w Mutex底层互斥锁保证同一时间只能有一个写协程readerCount读者计数器读写双方通信的标记readerWait记录写锁到来之前已经存在的读协程数量防止写锁永久等待写饥饿约定无写操作readerCount 0存在活跃写锁readerCount 0写入时减去 130三、四大接口底层执行流程1. Lock () 获取写锁先抢占内部互斥锁w保证写写互斥杜绝并发写给readerCount减去1 30将计数器置为负数这一步相当于打上标记当前有写操作正在排队后续新来的读锁全部阻塞将当前readerCount拷贝至readerWait代表写锁到达前已存在的读协程数量阻塞等待直到所有正在执行的旧读协程全部释放锁readerWait 0写协程正式执行业务。关键点写锁先标记、再等待。标记之后新读请求直接阻塞不会持续产生新读协程无休止抢占。2. Unlock () 释放写锁清除写标记readerCount 1 30唤醒所有因为写锁阻塞的读协程释放内部互斥锁w同时唤醒其他等待的写协程。3. RLock () 获取读锁原子操作readerCount增加读者计数判断readerCount是否为负数≥0无写操作直接获取读锁成功0说明当前存在正在等待 / 执行的写协程读协程阻塞等待信号量。逻辑总结只要写锁打上负数标记新读请求全部排队。4. RUnlock () 释放读锁原子操作readerCount--减少读者计数同时递减readerWait当readerWait 0代表写锁到来之前所有旧读协程全部退出唤醒等待中的写协程。四、三大竞争关系原理拆解4.1 写操作如何阻止其他写操作依靠结构体内置的Mutex w。 任何协程调用Lock()第一件事就是抢占该互斥锁。互斥锁天然排他同一时间只会有一个协程拿到锁执行写逻辑实现写写互斥。4.2 写操作如何阻止读操作写锁执行时执行readerCount - 1 30计数器变为负值。 后续所有协程调用RLock()时检测到readerCount 0判定存在写操作主动阻塞。注意写锁标记生效之后新来的读协程会阻塞但写锁到达前已经拿到读锁的协程可以继续执行写锁需要等待它们全部释放。4.3 读操作如何阻止写操作读协程执行RLock()会让readerCount 。 写协程拿到内部互斥锁之后需要等待readerWait归零也就是等待所有已有读协程调用RUnlock()。只要还有活跃读协程写协程持续阻塞。五、核心难题如何避免写锁饥饿什么是写饥饿如果没有readerWait机制持续不断的读协程源源不断到来readerCount永远大于 0写协程会无限等待永远无法获取锁。readerWait 解决方案写协程抢占内部互斥锁后复制当前readerCount到readerWaitreaderWait写锁到达这一刻已经存在的读协程总数只需要等待这一批 “旧读协程” 执行完毕每一次RUnlock()不仅递减readerCount同时递减readerWait当readerWait 0说明写锁到达前启动的读协程全部完成立刻唤醒写协程。这套机制的核心 写锁到来之后新产生的读协程会被阻塞不再新增任务保证写协程一定能等到窗口期从根源杜绝写饥饿。六、使用注意事项实践避坑锁配对RLock()必须搭配RUnlock()Lock()搭配Unlock()禁止混用禁止递归加锁同一个协程重复获取读锁 / 写锁会造成死锁适用场景读多写少读写频率接近时RWMutex 额外的计数器、信号量开销可能高于普通 Mutex不要拷贝RWMutex锁内部包含状态拷贝会产生无效锁。RWMutex设计十分巧妙 依靠内置Mutex实现写写互斥依靠readerCount作为标记协调读写冲突依靠readerWait解决并发经典的写饥饿问题。 读懂计数器正负含义、return 前锁执行时序、信号量唤醒机制就能彻底分清 Mutex 和 RWMutex 的选型场景。