美文网首页
Go - sync.RWMutex

Go - sync.RWMutex

作者: kyo1992 | 来源:发表于2021-05-15 10:08 被阅读0次

设计目的

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

限制

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

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 问题一般有三类,基于对读和写操作的优先级,读写锁的设计和实现也分成三类。

  • Read-preferring:读优先的设计可以提供很高的并发性,但是,在竞争激烈的情况下可能会导致写饥饿。这是因为,如果有大量的读,这种设计会导致只有所有的读都释放了锁之后,写才可能获取到锁。

  • Write-preferring:写优先的设计意味着,如果已经有一个 writer 在等待请求锁的话,它会阻止新来的请求锁的 reader 获取到锁,所以优先保障 writer。当然,如果有一些 reader 已经请求了锁的话,新请求的 writer 也会等待已经存在的 reader 都释放锁之后才能获取。所以,写优先级设计中的优先权是针对新来的请求而言的。这种设计主要避免了 writer 的饥饿问题。

  • 不指定优先级:这种设计比较简单,不区分 reader 和 writer 优先级,某些场景下这种不指定优先级的设计反而更有效,因为第一类优先级会导致写饥饿,第二类优先级可能会导致读饥饿,这种不指定优先级的访问不再区分读写,大家都是同一个优先级,解决了饥饿的问题。

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
  • 字段 w:为 writer 的竞争锁而设计;
  • 字段 readerCount:记录当前 reader 的数量(以及是否有 writer 竞争锁);
  • readerWait:记录 writer 请求锁时需要等待 read 完成的 reader 的数量;
  • writerSem 和 readerSem:都是为了阻塞设计的信号量。
  • 这里的常量 rwmutexMaxReaders,定义了最大的 reader 数量。

写锁加锁过程

  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.

加锁解锁总结

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

相关文章

  • Go - sync.RWMutex

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

  • GO 读写锁sync.RWMutex(1)

    继GO语言锁机制(1)后,我们继续来探讨GO语言的锁机制 sync.RWMutex (读写锁,R代表度,W代表写)...

  • Go超时锁的设计和实现

    Go提供两种锁:sync.Mutex和sync.RWMutex。 sync.Mutex: 互斥锁。任意时刻,只能有...

  • Golang学习笔记之互斥锁(Mutex)

    Go语言包中的sync包提供了两种锁,互斥锁(sync.Mutex)和读写锁(sync.RWMutex) 这一篇博...

  • GO 读写锁sync.RWMutex(2)

    读写锁首先是内置了一个互斥锁,然后再加上维护各种计数器来实现的读写锁,紧接着提供了四个函数支撑着读写锁操作,由 L...

  • GO sync.RWMutex - 解决并发读写问题

    当多个线程访问共享数据时,会出现并发读写问题(reader-writer problems)。有两种访问数据的线程...

  • go 中的 sync.Mutex 与 sync.RWMutex

    sync.Mutex 互斥锁,不支持递归锁 sync.RWMutex 什么时候用sync.Mutex,什么时候用 ...

  • Go 的 sync (同步原语)库

    官方包的注释: sync包提供基础的同步原语,sync.Mutext、sync.RWMutex、sync.Wait...

  • golang并发编程读写锁sync.RWMutex

    一、介绍sync.RWMutex 为读写锁,lock为写锁定 ,Unlock 为写解锁,RLock为读锁,RUn...

  • 读写锁和互斥锁 读写互斥锁,简称读写锁 mux sync.RWMutex Lock和Unlock分别对写锁进行锁定...

网友评论

      本文标题:Go - sync.RWMutex

      本文链接:https://www.haomeiwen.com/subject/uhtcjltx.html