表现
读写锁在表现上是允许并发读,独占写的。这把锁理解上可以看成一把读锁(共享锁),一把写锁(独占锁)
即调用读写锁时,一读线程持有读锁(RLock()),同时允许其它线程持有读锁,大家一起进行并发读。但是写线程(Lock())持有的写锁是独占锁的,当别人持有读或写锁,它就无法请求获得写锁。
总结:
writer 线程持有读写锁,这把锁就是写锁-独占锁
reader 线程持有读写锁,这把锁就是读锁-共享锁
RWMutex 结构体
type RWMutex struct {
w Mutex // 互斥锁
writerSem uint32 // reader 完成后会释放 writer信号量
readerSem uint32 // writer 完成后会释放 reader 信号量
readerCount int32 // 当前的 reader 的数量(以及用来判断是否有 writer 线程请求锁或者持有锁)
readerWait int32 // writer 等待 reader 完成的数量,到 0,writer 会被唤醒
}
const rwmutexMaxReaders = 1 << 30 // 表识最大 reader 线程数量
RLock()
func (rw *RWMutex) RLock() {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
// 当 reader 线程请求读锁,发现 readercout + 1 < 0
// 1. 为readerCount += 1
// 2. 判断 readerCount 是否小于 0
if atomic.AddInt32(&rw.readerCount, 1) < 0 {
// 证明有一个 writer 线程正在等待,阻塞自身,等待writer 线程完成
runtime_SemacquireMutex(&rw.readerSem, false, 0)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
}
}
其中值得注意的是:
- readerCount 为负数,表示当前时刻有 writer 线程正在等待写锁 或者 持有写锁,因为写锁是独占锁,这种情况下,reader 线程应当进入阻塞等待 writer 线程完成释放 readerSem 信号量
- readerCount 为正数,表示此时 reader 线程的数量
RUnLock()
func (rw *RWMutex) RUnlock() {
if race.Enabled {
_ = rw.w.state
race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
race.Disable()
}
// 将 readerCount 数量 - 1
// 判断此时 readerCount 数量是否小于 0
if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
// 如果此时 readerCount 数量小于 0,说明有 writer 线程等待锁
rw.rUnlockSlow(r)
}
if race.Enabled {
race.Enable()
}
}
func (rw *RWMutex) rUnlockSlow(r int32) {
if r+1 == 0 || r+1 == -rwmutexMaxReaders {
race.Enable()
throw("sync: RUnlock of unlocked RWMutex")
}
// 如果此时 readerWait 数量,写线程[需要等待]的 reader 线程已经全部完成(不是 readerCount) -- readerWait == 0
if atomic.AddInt32(&rw.readerWait, -1) == 0 {
// 则释放 writerSem 信号量唤醒 writer 线程
runtime_Semrelease(&rw.writerSem, false, 1)
}
}
值得注意的是:
readerWait 只有当 writer 线程请求锁时才会出现为非负数,此时它会拷贝readerCount 的大小,即 readerWait = readerCount, 如果咩有 writer 线程,则 readerWait 的值为 0, readerCount 的大小为正数
总结:
当 writer 线程请求锁时,比它后来的 reader 线程不会影响到 readerWait 的数量,即 后面的 reader 线程是无法与 writer 线程竞争锁的,讲究先来后到,避免写锁饥饿。
Lock()值得注意的是,因为写锁是一把独占锁,所以所有的 wrtier 线程是竞争的关系,利用 RWMutex 的结构体成员 sync.Mutex 互斥锁保证
func (rw *RWMutex) Lock() {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
// 首先与其他 writer 线程竞争
rw.w.Lock()
// 通过将 readerCount 的数量变为负数,告诉 reader 线程这里有 writer 线程正在等待锁
// 注意这里的 r 是局部变量
r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
// Wait for active readers.
// 判断 r ,不为 0 说明有 reader 线程持有锁
// 且将此时的 r 赋值给 readerWait 成员,判断 readerWait 的数量
if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
// 请求写信号量,阻塞自身,等待 reader 线程释放 writer 信号量
runtime_SemacquireMutex(&rw.writerSem, false, 0)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
race.Acquire(unsafe.Pointer(&rw.writerSem))
}
}
值得注意的是:
当 writer 线程请求锁后,此时真正活跃的 reader 线程只有 readerWait 的数量,后面的 reader 线程都因为 readerCount 为负数(有 writer 线程请求锁或者持有锁)陷入阻塞状态。
直到 active reader 线程都执行完毕,则唤醒 writer 线程。
UnLockfunc (rw *RWMutex) Unlock() {
if race.Enabled {
_ = rw.w.state
race.Release(unsafe.Pointer(&rw.readerSem))
race.Disable()
}
// 将 readerCount 数量变为正数,告诉 reader 线程,无 writer 线程了
r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
if r >= rwmutexMaxReaders {
race.Enable()
throw("sync: Unlock of unlocked RWMutex")
}
// 将此时所有的 reader 线程唤醒
for i := 0; i < int(r); i++ {
runtime_Semrelease(&rw.readerSem, false, 0)
}
// 释放互斥锁,唤醒 writer 线程竞争互斥锁
rw.w.Unlock()
if race.Enabled {
race.Enable()
}
}
参考资料
- 《Go 专家编程》作者:任洪彩 - 第五章 并发控制 - 书籍
- 《Go 并发编程实战课》作者:晁岳攀 - 基本并发原语 - 极客时间