我们在多协程操作时,有种场景是读操作次数远远大于写操作,这个时候,我们就会考虑用到读写锁。
读写锁(百科)定义:是一种特殊的的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。
在Go语言中、sync标准库定义了RWMutex结构代表读写锁,该结构在rwmutex.go中,RWMutex继承于Locker接口,其实是互斥锁的改进版,为什么这样说呢,看完下文就会清楚了。
读锁之间是共享的,多协程可以同时加读锁,写锁与读锁之间是互斥的。
源码基于:go version go1.13.4 windows/amd64。
Locker接口
type Locker interface {
Lock()
Unlock()
}
读写锁结构体定义
type RWMutex struct {
w Mutex // 互斥锁,写锁协程获取该锁后,其他写锁处于阻塞等待
writerSem uint32 // 写入等待信号量,最后一个读取协程释放锁时会释放信号量
readerSem uint32 // 读取等待信号量,持有写锁协程释放后会释放信号量
readerCount int32 // 读锁的个数
readerWait int32 // 写操作时,需要等待读操作的个数
}
我们看到RWMutex读写锁里面包含:
再看常量rwmutexMaxReaders,定义的是最大读锁数量
// rwmutexMaxReaders:支持1073741824个锁
const rwmutexMaxReaders = 1 << 30
sync.RWMutex所有方法如下图所示
读写锁的核心方法:其主要核心的有Lock、Unlock、RLock和RUnlock四个方法。
func (*RWMutex) Lock // 获取写锁操作
func (*RWMutex) Unlock // 解除写锁操作
func (*RWMutex) RLock // 获取读锁操作
func (*RWMutex) RUnlock // 解除读锁操作
RLocker() Locker // 返回Locker对象
RLocker,返回一个Locker对象
// 返回一个Locker对象
func (rw *RWMutex) RLocker() Locker {
return (*rlocker)(rw)
}
Lock,获取写锁操作
func (rw *RWMutex) Lock() {
// ①、竞争锁时检测
if race.Enabled {
_ = rw.w.state
race.Disable()
}
// 先获取Mutex互斥锁,多协程时只能有一个协程拿到锁
rw.w.Lock()
// ②、写锁时,会将readerCount减去rwmutexMaxReaders,
// 设置需要等待释放的读锁的数量,如有则挂起获取读锁的协程
r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
// ③如果先进来的,会在队列前面阻塞等待,进入队列等待
runtime_SemacquireMutex(&rw.writerSem, false, 0)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
race.Acquire(unsafe.Pointer(&rw.writerSem))
}
}
①写锁操作只能被一个写协程拿到,竞争检测后进行互斥锁上锁Mutex.lock,,多协程时只能有一个协程拿到锁
②写锁时,readerCount减去rwmutexMaxReaders,readerCount会变成很大的负数,读锁时readerCount会+1
③如果readerCount不等于0,协程获取到读锁,如果满足上锁条件时,会调用runtime_SemacquireMutex,如果先进来的会在队列前面阻塞等待的信号量为writerSem,进入队列等待。
Unlock,解除写锁操作
func (rw *RWMutex) Unlock() {
// ①竞争检测
if race.Enabled {
_ = rw.w.state
race.Release(unsafe.Pointer(&rw.readerSem))
race.Disable()
}
// ②需要回复readerCount,上锁的时候减了常量rwmutexMaxReaders,这里再加回来
r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
// ③对未被写锁定的读写锁进行写解锁,会报错
if r >= rwmutexMaxReaders {
race.Enable()
throw("sync: Unlock of unlocked RWMutex")
}
// ④呼唤阻塞的读操作
for i := 0; i < int(r); i++ {
runtime_Semrelease(&rw.readerSem, false, 0)
}
// ⑤释放锁,如果有其他阻塞写操作,在此唤醒
rw.w.Unlock()
if race.Enabled {
race.Enable()
}
}
①解除写锁操作时,竞争检测
②需要回复readerCount,上锁的时候减了常量rwmutexMaxReaders,这里再加回来,加完之后如果大于等于rwmutexMaxReaders
③对未被写锁定的读写锁进行写解锁,会抛异常
④呼唤阻塞的读操作
⑤释放锁,如果有其他阻塞写操作,在此唤醒
RLock,获取读锁操作
func (rw *RWMutex) RLock() {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
// ①读锁时readerCount+1操作,当值小于0时会调用runtime_SemacquireMutex,表明写锁操作正在等待
if atomic.AddInt32(&rw.readerCount, 1) < 0 {
// 当写锁操作时,读锁也会阻塞等待
runtime_SemacquireMutex(&rw.readerSem, false, 0)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
}
}
①①读锁时readerCount+1操作,当值小于0时会调用runtime_SemacquireMutex,表明写锁操作正在等待
②当写锁操作时,读锁也会阻塞等待,读锁加入等待队列
RUnlock,解除读锁操作
func (rw *RWMutex) RUnlock() {
if race.Enabled {
_ = rw.w.state
race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
race.Disable()
}
// ①直接将readerCount-1操作
if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
// ②如果r小于0,说明有协程正在获取写操作锁,
// 当readerWait减到0时说明没有协程持有写操作锁,
// 就通过信息号wrirerSem通知等待的协程来争抢锁
rw.rUnlockSlow(r)
}
if race.Enabled {
race.Enable()
}
}
①解除读锁,直接对readerCount-1操作,这里就是把之前+1操作的再减回来
②如果r小于0,说明有协程正在获取写操作锁,当readerWait减到0时说明没有协程持有写操作锁,就通过信息号wrirerSem通知等待的协程来争抢锁
本文为原创文章,出自guichenglin,转载请粘贴源链接,如果未经允许转发后果自负。