RWMutex核心还是基于Mutex的,如果想了解Mutex的话可以看一下我上一篇写的Mutex的文章RWMutex的特性就是支持并发读。适用于读多写少的场景。
RWMutex的定义
type RWMutex struct {
w Mutex // 互斥锁
writerSem uint32 // 写锁用的信号量
readerSem uint32 // 读锁用的信号量
readerCount int32 // 当前正在执行读操作的goroutine数量
readerWait int32 // 获取写锁时,当前还持有读锁的goroutine数量
}
const rwmutexMaxReaders = 1 << 30
RWMutex.Lock()
func (rw *RWMutex) Lock() {
// 首先调用Mutex的Lock方法获取到锁
rw.w.Lock()
// 把readerCount改成负数,这样后续的读操作就会被阻塞
// r 就是当前正在执行读操作的goroutine数量
r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
// 如果当前有正在执行读操作的goroutine
// 把r赋值给readerWait
if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
// 获取写锁的goroutine进入休眠,等待被唤醒
runtime_SemacquireMutex(&rw.writerSem, false, 0)
}
}
RWMutex.Unlock()
func (rw *RWMutex) Unlock() {
// 把readerCount改成正数,这样后续读操作就不会被阻塞了
r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
...
// 手动唤醒之前被写锁阻塞的读操作goroutine
for i := 0; i < int(r); i++ {
runtime_Semrelease(&rw.readerSem, false, 0)
}
// 释放互斥锁,其他写锁就可以竞争互斥锁了
rw.w.Unlock()
}
RWMutex.RLock()
func (rw *RWMutex) RLock() {
...
// readerCount + 1
if atomic.AddInt32(&rw.readerCount, 1) < 0 {
// 小于0,说明有其他goroutine获取了写锁, 当前goroutine等待
runtime_SemacquireMutex(&rw.readerSem, false, 0)
}
...
}
RWMutex.RUnlock()
func (rw *RWMutex) RUnlock() {
...
// readerCount - 1
// readerCount < 0, 说明其他gouroutine获取了写锁,正在等待还持有读锁的goroutine释放读锁
// readerCount >= 0, 说明没有写锁被阻塞,直接返回就行了
if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
// 释放读锁
rw.rUnlockSlow(r)
}
...
}
func (rw *RWMutex) rUnlockSlow(r int32) {
...
// readerWait - 1
// 判断当前goroutine是不是最后一个释放读锁
if atomic.AddInt32(&rw.readerWait, -1) == 0 {
// 唤醒写锁
runtime_Semrelease(&rw.writerSem, false, 1)
}
}
总结
获取读锁的流程
- readerCount + 1
- 以readerCount<0,判断是否被写锁阻塞,是的话,当前goroutine进入休眠
释放读锁的流程
- readerCount - 1
- 以readerCount<0,判断是否有写锁
- 没有写锁的话,直接返回
- 有写锁的话,调用rUnlockSlow方法,readerWait - 1
- 如果readerWait == 0, 说明当前goroutine是写锁等待的最后一个读锁goroutine,需要唤醒写锁goroutine
获取写锁的流程
- 先获取互斥锁
- readerCount - rwmutexMaxReaders,后续读操作全部阻塞
- readerWait += readerCount,把当前正在执行读操作的数量加到readerWait上
- 如果readerWait != 0 ,说明当前还有其他goroutine持有读锁,当前goroutine进入睡眠,等待唤醒
释放写锁流程
- readerCount + rwmutexMaxReaders, 后续读锁不会阻塞
- readerCount代表之前被写锁阻塞的读锁goroutine个数,唤醒readerCount个读锁goroutine
- 最后释放互斥锁
最后
RWMutex相对Mutex,增加了读锁的控制,就代码逻辑复杂度而言,RWMutex比Mutex要简单很多,对Mutex的流程熟悉的话,很快就能掌握RWMutex的原理
有疑问加站长微信联系(非本文作者)