读写锁适用于多写少读的场景,同时只允许一个写进程存在,多个读操作可以并发进行,设计一个高并发可行的读写锁是一个值得思考的工程问题
难点
- 保证多个读操作并发,同时阻塞写操作
- 保证写操作与写操作之间互斥,同时不能有其他读操作
- 读操作和写操作可以相互感知【当有读操作时写操作被阻塞,读操作都结束时可以唤醒写操作,反之也成立】
总的来说可以概括为以下表格
| 互斥 | 读 | 写 |
|---|---|---|
| 读 | Y | N |
| 写 | N | N |
其实要解决的问题就是三个,分别是读读共享、写写互斥、读写互斥。进一步分析,其实就是解决以下问题:
- 多个读操作时共享锁,
- 多个写操作时只有一个可以获取锁
- 读写有一个存在时进入等待队列
- 满足条件时释放锁
- 读、写线程可以相互感知对方存在,
下面分析讨论golang官方的sync.RWMutex是如何解决以上问题的。
读读共享
多个读操作获取锁时,读操作计数+1,标记当前的读线程数
atomic.AddInt32(&rw.readerCount, 1)
读写互斥
已有写操作,读操作获取锁时
if atomic.AddInt32(&rw.readerCount, 1) < 0 {
//readerCount+1<0说明存在写线程,该进入读信号量的等待队列
runtime_SemacquireMutex(&rw.readerSem, false, 0)
}
已有读操作,写操作获取锁时
//r表示当前读线程的个数
r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
//将readerWait加上当前读进程的个数,表示当前写进程需要等待readerCount个读线程释放锁
if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
//如果有其他读进程或者需要等待读进程释放个数>0(即readerCount>0),则进入写信号量等待队列
runtime_SemacquireMutex(&rw.writerSem, false, 0)
}
写线程存在的通知
//readerCount-rwmutexMaxReaders,让readerCount为负数,通知读线程此时有写线程
atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders)
//readerCount+rwmutexMaxReaders,让readerCount为非负数,通知读线程写线程已处理完毕
atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
写写互斥
//通过互斥锁来实现
rw.w.Lock()
深入sync.RWMutex源码
在解决以上三个问题后再来具体看完整的源码(go1.16)
type RWMutex struct {
w Mutex //用于写写互斥的锁
writerSem uint32 //等待读线程完成的信号量
readerSem uint32 //等待写线程完成的信号量
readerCount int32 //现有的读线程数
readerWait int32 //写线程需要等待释放的读线程数
}
const rwmutexMaxReaders = 1 << 30
//获取读锁
func (rw *RWMutex) RLock() {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
//现有的读线程数+1
if atomic.AddInt32(&rw.readerCount, 1) < 0 {
//readerCount<0,表示此时写线程已经获取的锁,进入等待队列,等待写线程完成释放信号量
//readerCount在写线程存在时会减去rwmutexMaxReaders
runtime_SemacquireMutex(&rw.readerSem, false, 0)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
}
}
//释放读锁
func (rw *RWMutex) RUnlock() {
if race.Enabled {
_ = rw.w.state
race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
race.Disable()
}
//现有的读线程数-1
if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
//readerCount<0,表示已有一个写线程在等待释放读锁
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-1,表示写线程需要等待释放的读锁数-1
if atomic.AddInt32(&rw.readerWait, -1) == 0 {
//如果所有读锁都释放了,则释放写信号量,唤醒写线程
runtime_Semrelease(&rw.writerSem, false, 1)
}
}
//获取写锁
func (rw *RWMutex) Lock() {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
//写写互斥
rw.w.Lock()
//将readerCount-rwmutexMaxReaders通知其他读线程,存在一个写线程,r等于当前读线程数
r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
//readerWait为该写线程需要等待的读锁释放个数
if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
//当前读线程数>0或者需要等待释放的读锁个数>0,进入写信号等待队列
runtime_SemacquireMutex(&rw.writerSem, false, 0)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
race.Acquire(unsafe.Pointer(&rw.writerSem))
}
}
//释放写锁
func (rw *RWMutex) Unlock() {
if race.Enabled {
_ = rw.w.state
race.Release(unsafe.Pointer(&rw.readerSem))
race.Disable()
}
//将readerCount+rwmutexMaxReaders,通知其他读线程,写线程已处理完毕
//r表示当前读线程的个数
r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
if r >= rwmutexMaxReaders {
race.Enable()
throw("sync: Unlock of unlocked RWMutex")
}
//循环释放r个读信号量,以唤醒所有读线程
for i := 0; i < int(r); i++ {
runtime_Semrelease(&rw.readerSem, false, 0)
}
//允许其他写线程获取锁
rw.w.Unlock()
if race.Enabled {
race.Enable()
}
}