1、介绍
在sync.Mutex基础上实现的读写锁,支持读读并发,其他还是串行,适用于读多写少的场景
2、结构体
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
}
-
w :一个
Mutex
,仅用于解决多个writer之间的竞争问题, -
writerSem:一个信号量,用于阻塞writer等待正在进行的reader完成。就是用于写操作加锁时候要等待所有的读操作完成,当所有读操作解锁结束后,会发送信号,然后写操作被唤醒
-
readerSem:一个信号量,用于阻塞reader等待正在进行的writer完成。读操作要获取锁时候,需要等待写操作解锁,解锁后向该信号量发送信号,解锁所有读操作
-
readerCount:记录当前正在进行的reader的数量,也用于表示是否有writer正在等待。
-
readerWait :记录writer请求锁时需要等待完成的reader的数量。待完成的读操作计数器,仅有写操作等待时候有用,代表在写操作前已经在执行的读操作数量
核心流程
3.1 读加锁
Go
func (rw *RWMutex) RLock() {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
if rw.readerCount.Add(1) < 0 {
// A writer is pending, wait for it.
runtime_SemacquireRWMutexR(&rw.readerSem, false, 0)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
}
}
原子操作 readCount 直接加1,如果结果不小于0,则说明加锁成功
如果小于0,则说明当前有writer正在等待或已经持有锁,则当前goroutine会阻塞在readerSem上,直到没有writer持有锁
注意: readCount小于0的原因是有写操作加锁时候会readCount-2的30次方,导致其值小于0
3.2 读解锁
func (rw *RWMutex) RUnlock() {
if race.Enabled {
_ = rw.w.state
race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
race.Disable()
}
if r := rw.readerCount.Add(-1); r < 0 {
// Outlined slow-path to allow the fast-path to be inlined
rw.rUnlockSlow(r)
}
if race.Enabled {
race.Enable()
}
}
func (rw *RWMutex) rUnlockSlow(r int32) {
if r+1 == 0 || r+1 == -rwmutexMaxReaders {
race.Enable()
fatal("sync: RUnlock of unlocked RWMutex")
}
// A writer is pending.
if rw.readerWait.Add(-1) == 0 {
// The last reader unblocks the writer.
runtime_Semrelease(&rw.writerSem, false, 1)
}
}
原子操作 readCount 直接减1,如果结果不小于0,说明无写协程等待,直接解锁完成
如果小于0,说明有写协程正在等待,进入慢流程:
原子操作把readerWait减1,若等于0 说明所有读操作都执行结束了,向writerSem发送信号,去唤醒等待的写协程。
大于0,说明还有其他读操作,则不发送信号量直接返回,解锁结束

3.3 写加锁
保证写操作互斥,等待所有的读操作结束
func (rw *RWMutex) Lock() {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
// 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)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
race.Acquire(unsafe.Pointer(&rw.writerSem))
}
}
先获取mutex的lock锁,确保同时只有一个写操作能进入后续
获取到互斥锁后,将readerCount设置为一个负数(通常是-readerCount-1
),表示有writer正在等待锁。如果有正在进行的reader,writer会阻塞在writerSem上,直到所有reader都释放了锁。
r := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders
精妙之处在于,先把readCount变为负数(使得后面的再进来的读操作进入阻塞),然后又加回来赋值给临时变量r,走到后面判断,如果有读操作还在运行,把readWait设置为原readCount的值,如果大于0,则写操作进入阻塞太,等待所有读操作完成
if r != 0
- 如果
r == 0
,说明没有活跃读者,writer 可以直接获得锁,无需等待。 - 如果
r > 0
,说明有r
个读者正在读,需要等他们退出。
b. rw.readerWait.Add(r) (如果有读操作在运行,则肯定大于0)
- 将
readerWait
设置为r
(因为初始为 0,Add(r)
后就是r
); readerWait
表示:"我这个 writer 需要等r
个读者退出"。
⚠️ 注意:这里
Add(r)
的返回值是更新后的值 。由于初始为 0,返回值就是r
,所以!= 0
恒成立(只要r != 0
)。
c. runtime_SemacquireRWMutex(&rw.writerSem, false, 0)
- 调用底层 runtime 函数,在
writerSem
信号量上阻塞当前 goroutine; - 会一直 sleep,直到有读者在
RUnlock()
时发现readerWait == 0
,然后runtime_Semrelease
唤醒它。
✅ 此时,writer 被挂起,等待所有"已进入"的读者退出。
3.4 写解锁
func (rw *RWMutex) Unlock() {
if race.Enabled {
_ = rw.w.state
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()
}
}
释放写锁。将readerCount恢复为正数(通过加上一个常数,通常是rwmutexMaxReaders 2的30次方
),表示writer已经释放了锁,此时如果有等待的reader或writer,它们可以根据情况被唤醒。
如果r大于0,代表有多个r还在等待,则通过runtime_Semrelease(&rw.readerSem, false, 0) 发送解锁信号量,唤醒读操作
等于0 则说明 没有读操作在等下,继续往下,
执行mutex.Unlock互斥锁解锁,写操作解锁完成,解锁过程中通知其他写操作被唤醒