一、RWMutex是什么?
RWMutex是一种读写互斥锁。该锁可以由任意数量的读取器或单个写入器持有。
RWMutex的0值是一个未锁定的互斥锁。
RWMutex在第一次使用后不能被复制。
如果一个goroutine持有一个用于读取的RWMutex,而另一个goroutine可能会调用Lock,那么在读锁被释放之前,没有goroutine应该能够获得读锁。特别是,这禁止递归读锁定。这是为了确保锁最终变得可用;
二、RWMutex 结构体
type RWMutex struct {
w Mutex // 互斥锁
writerSem uint32 // 写入器等待读取器完成的信号量
readerSem uint32 // 让读取器等待写入器完成的信号量
readerCount int32 // 正在读取的goroutine数量
readerWait int32 // 正在等待读取的goroutine数量
}
w(Mutext)在这里的作用:如Mutex中有等待的goroutine,RWMutex会暂停使用,直到写锁完全释放。
到这里,应该可以直到RWMutex是一个写优先的互斥锁.
三、RWMutex 方法
1.RLock - 获取读锁
func (rw *RWMutex) RLock() {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
if atomic.AddInt32(&rw.readerCount, 1) < 0 {
runtime_SemacquireMutex(&rw.readerSem, false, 0)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
}
}
RLock很简单,先操作readerCount原子性(AddInt32) + 1,通过返回值来判断当前是否有goroutine持有写锁;若有goroutine持有写锁,则当前goroutine进入休眠,在readerSem信号中排队(排到最后一位).
2.RUnlock - 释放读锁
func (rw *RWMutex) RUnlock() {
if race.Enabled {
_ = rw.w.state
race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
race.Disable()
}
if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
// 慢路径释放读锁
rw.rUnlockSlow(r)
}
if race.Enabled {
race.Enable()
}
}
同样,释放读锁是readerCount来判断当前是否有goroutine持有写锁。
func (rw *RWMutex) rUnlockSlow(r int32) {
//判断当前读取goroutine的数量(readerCount),若没有正在读取的,则panic
if r+1 == 0 || r+1 == -rwmutexMaxReaders {
race.Enable()
throw("sync: RUnlock of unlocked RWMutex")
}
// 若等待读取的goroutine(readerWait)数量为0,则唤醒等待的写锁(writerSem)
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()
}
// 首先获取写锁,通过Mutex来实现
rw.w.Lock()
// 修改readerCount值,将当前正在读取的goroutine修改为负数
r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
// 修改readerWait值,将readerCount数量添加到readerWait;readerWait += readerCount
if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
runtime_SemacquireMutex(&rw.writerSem, false, 0)//将当前goroutine进行睡眠,并排队到writerSem队列的队尾
}
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()
}
// 恢复readerCount值
r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
if r >= rwmutexMaxReaders {//若readerCount异常,则panic
race.Enable()
throw("sync: Unlock of unlocked RWMutex")
}
// 唤醒所有等待获取读锁的goroutine
for i := 0; i < int(r); i++ {
runtime_Semrelease(&rw.readerSem, false, 0)
}
// Allow other writers to proceed.
rw.w.Unlock() //释放写锁(Mutex.Unlock)
if race.Enabled {
race.Enable()
}
}
通过上面的源码分析,可以看到获取读锁/写锁时,当获取失败时,通过信号量(readerSem/writerSem)进入睡眠并排在相应队列的队尾;而在释放读锁/写锁时,也是通过相应的信号量(readerSem/writerSem)进行唤醒。
为什么RWMutex是写优先?
在Lock方法中,获取写锁时,是直接使用Mutex.Lock方法获取,而不是等待获得读锁的goroutine读取完成后,进行释放。
Mutex.Lock方法在获取失败时,会进行自旋、进入睡眠,直到获得锁。https://mp.csdn.net/mp_blog/creation/editor/125683672