总结
- 实现的原理相对Mutex较简单,使用Mutex、信号量和atomic实现
- 写锁之间的互斥通过Mutex互斥锁实现
- 写锁和读锁之间的互斥通过信号量和原子操作实现,巧妙将readerCount减去一个大值1<30,然后配合上信号量实现读锁和写锁间的互斥
对比Mutex
读写锁相对与互斥锁,读写锁在读上锁的时候,大部分场景只需要通过atomic进行加操作即可,不需要其它复杂操作
关键字
Mutex、信号量、atomic实现
场景
多个写锁
- 多个写锁进行上锁,通过Mutex互斥锁实现互斥,保证当前只有一个写锁,进入上锁状态
写锁上锁后,进行写锁
- 因为写锁上锁后,会通过原子加操作将readerCount加上一个负值-1<30
- 后续读锁上锁时,通过原子加操作将readerCount加1,此时仍小于0,会去申请readerSem信号量,这时readerSem等于0,因此当前goroutine会被阻塞。
- 当前写锁进行Unlock时,通过原子加操作,将readerCount加上1<30,这时会获取到等待读锁加锁的个数,然后通过释放readerSem信号量,
这时读锁就能进行加锁成功了
多个读锁成功后,进行写锁加锁
- 通过原子加操作将readerCount加上1,几个写锁加锁,就会加上几
- 写锁加锁,因为readerCount>0,进行r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders,r会大于0
- 将readerCount原子加操作加上r后,大于0,会去申请writerSem信号量,因为等于0,因此当写锁会被阻塞
- 读锁解锁时,会将readerWait通过原子加操作加上-1,通过判断readerWait是否等于0判读是否为最后一个读锁解锁,最后一个读锁解锁,会释放一个writerSem信号量,从而写锁能够获取到信号量,加锁成功。
代码注释说明
代码结构
sync/rwmutex.go里面实现
使用方式
主要包括四个函数
写锁操作
Lock:写操作上锁,如果有写锁存在,其它操作被阻塞
UnLock:写操作解锁
读锁操作
RLock:读操作上锁,如果读锁存在,写锁阻塞,读锁支持多个goroutine加锁,各个读锁之间不阻塞
RUnlock:读操作解锁
代码走读
结构体说明
- mutex/rwmutex.go是runtime/rwmutex.go里修改后的副本
- 多个读锁间并行,写锁间互斥,读锁和写锁间互斥。RWMutex的零值是未上锁状态
- RWMutex初始化后,不能被复制拷贝
- 读锁禁止递归调用,防止最终能获取到锁,出现死锁
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 int32 // number of pending readers 阻塞的读锁的个数
readerWait int32 // number of departing readers 当前读锁的个数,用作实现读锁和写锁之间的互斥
}
写锁
- 写锁之间是互斥的
- 读锁与写锁之间互斥
上锁
func (rw *RWMutex) Lock() {
// First, resolve competition with other writers.
// 实现写锁唯一,其它goroutine再进行写锁,或被阻塞
rw.w.Lock()
// Announce to readers there is a pending writer.
// 通过atomic操作将readerCount赋值为负值,rwmutexMaxReaders大小为1<30,
// 初始值readerCount大小为0,此次操作后赋值为-1<30,r能为0
r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
// Wait for active readers.
// 如果r不为0,则说明readerCount大于0,即当前有读锁已经进入了上锁状态
// 如果readerWait+r不为0,则表明有读锁存在,则进行获取写锁信号量,因为writerSem默认值为0,被写锁被阻塞
// 后面对readerWait的原子加操作,是记录当前读锁上锁个数,
// 用做RUnlock里面释放读锁时,最后一个读锁释放,释放一个writerSem信号量
if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
runtime_SemacquireMutex(&rw.writerSem, false, 0)
}
}
解锁
- 写锁解锁
- 如果没有进行写上锁,直接写解锁,会抛异常
- 写上锁和写解锁的goroutine可能不一样
func (rw *RWMutex) Unlock() {
// Announce to readers there is no active writer.
// 通过原子操作将readerCount加上1<30,
r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
if r >= rwmutexMaxReaders {
race.Enable()
throw("sync: Unlock of unlocked RWMutex")
}
// Unblock blocked readers, if any.
// 解锁阻塞的读锁的goroutines,通过释放readerSem信号量,r为几,则有几个reader
// 下面几行代码的实现的场景是:写锁已经上锁,这时有其它读锁进来上锁,因为
// 写锁存在,读锁上锁都会被阻塞,因此释放写的时候,会释放当前被阻塞
// 的读锁的个数的信号量,这样读锁就能获取到信号量,进行上锁了
for i := 0; i < int(r); i++ {
runtime_Semrelease(&rw.readerSem, false, 0)
}
// Allow other writers to proceed.
// 解锁写锁的大锁w,这样其它的goroutine就能进行写上锁了
rw.w.Unlock()
}
读锁
读锁之间是非互斥的,可以多个goroutine进行读锁操作,每个都能获取到,与写锁之间是互斥的
- 写锁存在,写锁上锁会被阻塞
上锁
func (rw *RWMutex) RLock() {
// 将readerCount加1,如果小于0,则说明已经有写锁进行了加锁,写锁会通过原子加操作将readerCount设置负值
// 小于0,则通过获取信号量readerSem,因为readerSem默认为值为0,则会
if atomic.AddInt32(&rw.readerCount, 1) < 0 {
// A writer is pending, wait for it.
// 获取readerSem信号量,只有写锁已经存在,才会进入这里,读锁存在,readerSem为0,当前goroutine会被阻塞
runtime_SemacquireMutex(&rw.readerSem, false, 0)
}
}
解锁
func (rw *RWMutex) RUnlock() {
// 通过原子加操作,将readerCount减一,如果小于0,则表明当前已经写锁加锁
if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
// Outlined slow-path to allow the fast-path to be inlined
rw.rUnlockSlow(r)
}
}
func (rw *RWMutex) rUnlockSlow(r int32) {
// 未进行RLock,直接进行RUnlock会抛异常
if r+1 == 0 || r+1 == -rwmutexMaxReaders {
race.Enable()
throw("sync: RUnlock of unlocked RWMutex")
}
// A writer is pending.
// readerWait只有会在两个地方进行赋值,在写上锁时
// 释放最有一个读锁时,释放一个writerSem信号量,然后写锁可以获取到信号量,加锁成功。
if atomic.AddInt32(&rw.readerWait, -1) == 0 {
// The last reader unblocks the writer.
runtime_Semrelease(&rw.writerSem, false, 1)
}
}