一、核心数据结构
type RWMutex struct {
w Mutex // held if there are pending writers
writerSem uint32 // semaphore for writers to wait for completing readers 唤醒write的信号量
readerSem uint32 // semaphore for readers to wait for completing writers 唤醒read的信号量
readerCount int32 // number of pending readers 读锁计数,write lock的时候,会设置成一个很大的负数来表示标识,阻塞后面的read
readerWait int32 // number of departing readers 解除lock的reader数量(负数)
}
源码注释提及到了,RWMutex在第一次使用之后不能被copy,禁止递归使用read lock.数据结构表明了设计思想,依赖Mutex、信号量、reader计数来实现读写互斥
二、核心函数
1、RLock
func (rw *RWMutex) RLock() {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
// 说明readerCount是个负数,应为write lock的时候会加上一个很大的负数来声明,有write lock来了,后面来的read都要阻塞,直到write unlock
if atomic.AddInt32(&rw.readerCount, 1) < 0 {
// A writer is pending, wait for it.
runtime_SemacquireMutex(&rw.readerSem, false, 0)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
}
}
2、RUnlock
func (rw *RWMutex) RUnlock() {
if race.Enabled {
_ = rw.w.state
race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
race.Disable()
}
// r<0,说明有write在阻塞,需要唤醒
// 只存在read的情况下,r最小值应该是0(除非重复调用runlock)
if r := atomic.AddInt32(&rw.readerCount, -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) {
// 判断是否是重复解锁,重复解锁直接panic
if r+1 == 0 || r+1 == -rwmutexMaxReaders {
race.Enable()
throw("sync: RUnlock of unlocked RWMutex")
}
// A writer is pending.
if atomic.AddInt32(&rw.readerWait, -1) == 0 {
// The last reader unblocks the writer.
runtime_Semrelease(&rw.writerSem, false, 1)
}
}
3、Lock
func (rw *RWMutex) Lock() {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
// First, resolve competition with other writers.
// 多个write锁,需要依次等待
rw.w.Lock()
// Announce to readers there is a pending writer.
// 声明有write lock请求
r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
// Wait for active readers.
// r!=0 代表还有已经有reader hold lock
// 解除lock的数量不等于持有lock的read,需要阻塞
if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
runtime_SemacquireMutex(&rw.writerSem, false, 0)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
race.Acquire(unsafe.Pointer(&rw.writerSem))
}
}
4、Unlock
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 := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
if r >= rwmutexMaxReaders {
race.Enable()
throw("sync: Unlock of unlocked RWMutex")
}
// Unblock blocked readers, if any.
// 解除所有被阻塞的read lock
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()
}
}
三、总结
关于RWMutex可以分成四种场景RR、WW、RW、WR
1、RR场景
在两次读的时候对readerCount加1,不会小于0,所以不会阻塞
2、WW场景
首先抢占mutex排队进入,进入之后,将readerCount加上一个-rwmutexMaxReaders来申声明有write操作,然后判断readerCount是否==0,还有已经在write来之后已经解除lock的read和目前还持有lock的read数量是否一致。由于没有read lock所以不会在信号量上等待。所以两个WW只通过mutex来互斥
3、RW场景
read获取lock对readerCount加1,write获取mutex,然后将readerCount加上一个-rwmutexMaxReaders来声明有write操作,判断readerCount原始的值不等于0,并且在write来之后解除read lock的数量不等于readerCount原始的值,write在writerSem信号量上阻塞,等待unlock read唤醒
r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
runtime_SemacquireMutex(&rw.writerSem, false, 0)
}
之所以需要判断atomic.AddInt32(&rw.readerWait, r) != 0是应为在计算r到判断r!=0之间,可能read lock就已经释放了,此时又去阻塞在writerSem信号量上,则会死锁
4、WR场景
write获取mutex,然后将readerCount加上一个-rwmutexMaxReaders来声明有write操作,判断readerCount原始的值等于0,不阻塞在writerSem信号量上,read来之后,对readerCount加1,发现结果小于0,就阻塞在readerSem的信号量上,等待unlock write唤醒
综上,采用了原子操作,所以是能够保证执行顺序,在并发的时候能够出现顺序
// Happens-before relationships are indicated to the race detector via:
// - Unlock -> Lock: mutex
// - Unlock -> RLock: readerSem
// - RUnlock -> Lock: writerSem