Go - sync.RWMutex

2021-05-15  本文已影响0人  kyo1992

设计目的

大多数读请求之间互不影响,在读多写少的场景下,可以分离读写操作,提高读写并发性能.

限制

只能读读并发, 读写, 写写操作不并发

RWMutex

RWMutex 在某一时刻只能由任意数量的 reader 持有,或者是只被单个的 writer 持有。

RWMutex 的方法总共有 5 个。

  1. Lock/Unlock:写操作时调用的方法。如果锁已经被 reader 或者 writer 持有,那么,Lock 方法会一直阻塞,直到能获取到锁;Unlock 则是配对的释放锁的方法。
  2. RLock/RUnlock:读操作时调用的方法。如果锁已经被 writer 持有的话,RLock 方法会一直阻塞,直到能获取到锁,否则就直接返回;而 RUnlock 是 reader 释放锁的方法。
  3. RLocker:这个方法的作用是为读操作返回一个 Locker 接口的对象。它的 Lock 方法会调用 RWMutex 的 RLock 方法,它的 Unlock 方法会调用 RWMutex 的 RUnlock 方法。

RWMutex 的零值是未加锁的状态,所以,当你使用 RWMutex 的时候,无论是声明变量,还是嵌入到其它 struct 中,都不必显式地初始化。

使用场景

如果遇到可以明确区分 reader 和 writer goroutine 的场景,且有大量的并发读、少量的并发写,并且有强烈的性能需求,你就可以考虑使用读写锁 RWMutex 替换 Mutex。

实现原理

RWMutex 是基于 Mutex 实现的

readers-writers 问题一般有三类,基于对读和写操作的优先级,读写锁的设计和实现也分成三类。

Go 标准库中的 RWMutex 设计是 Write-preferring 方案。一个正在阻塞的 Lock 调用会排除新的 reader 请求到锁。

RWMutex 包含一个 Mutex,以及四个辅助字段 writerSem、readerSem、readerCount 和 readerWait:

type RWMutex struct {
  w           Mutex   // 互斥锁解决多个writer的竞争
  writerSem   uint32  // writer信号量
  readerSem   uint32  // reader信号量
  readerCount int32   // reader的数量
  readerWait  int32   // writer等待完成的reader的数量
}

const rwmutexMaxReaders = 1 << 30

写锁加锁过程

  1. 尝试获取写锁,如果锁被占用,则本goroutine会进入自旋或者休眠.
  2. 判断当前执行读操作协程数量,如果不为0,先设置要等待的读操作数量,然后设置等待写等待读信号量进入休眠状态,等待所有读锁执行结束后释放信号量将当前协程唤醒.

写锁解锁过程

  1. 通过设置readCount成正数,释放读锁.
  2. for循环释放所有因为获取读锁而陷入等待的Groutine.
  3. 释放写锁.

读锁加锁过程

  1. 直接对readCount进行+1原子操作,>0则代表没有goroutine获取写锁, 读锁获取成功.
  2. 如果<0则代表有goroutine获取写锁,等待读等待写信号量进入休眠状态,等待写锁执行结束后释放信号量.

读锁解锁过程

  1. 直接对readCount进行-1原子操作,如果>=0代表释放成功.
  2. 如果<0,代表有写操作在等待读锁释放,将readerWait数量-1,如果结果==0,则触发写等待读信号量唤醒尝试获取写锁的goroutine.

加锁解锁总结

获取写锁时会先阻塞写锁的获取,后阻塞读锁的获取,解锁时先释放读锁,唤醒等待的读操作,再释放写锁,这种策略能够保证读操作不会被连续的写操作『饿死』。

上一篇下一篇

猜你喜欢

热点阅读