前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go高阶12,手摸手带你深入了解 RWMutex 实现原理

Go高阶12,手摸手带你深入了解 RWMutex 实现原理

作者头像
微客鸟窝
发布2021-09-10 15:40:04
3890
发布2021-09-10 15:40:04
举报
文章被收录于专栏:Go语言指北Go语言指北

之前我们介绍了互斥锁 Mutex,今天再来介绍下 RWMutex,即读写锁。读写锁是对 Mutex 的改进,在程序中,如果存在读操作多,写操作少的场景,使用 RWMutex 相比 Mutex 的并发能力会有很大的提升。

关于 RWMutex 的使用可以看下之前的文章介绍:并发控制,同步原语 sync 包,下面我们来看下读写锁的实现。

数据结构

源码包 src/sync/rwmutex.go 中定义了读写锁数据结构:

代码语言:javascript
复制
type RWMutex struct {
 w           Mutex  // held if there are pending writers
 writerSem   uint32 // semaphore for writers to wait for completing readers
 readerSem   uint32 // semaphore for readers to wait for completing writers
 readerCount int32  // number of pending readers
 readerWait  int32  // number of departing readers
}
  • w:用于控制多个写锁,获得写锁首先要获取该锁,如果有一个写锁在进行,那么再到来的写锁将会阻塞于此
  • writerSem:写阻塞等待的信号量,最后一个读者释放锁时会释放信号量
  • readerSem:读阻塞的协程等待的信号量,持有写锁的协程释放锁后会释放信号量
  • readerCount:记录读者的个数
  • readerWait:记录写阻塞时读者的个数

由此可见,读写锁内部还有一个互斥锁,用来将两个写操作隔离开,其他的几个用于隔离读操作和写操作。

接口定义

RWMutex 提供了 4个接口来:

  • Rlock() : 读锁定
  • RUnlock() : 解除读锁定
  • Lock() : 写锁定,与 Mutex 一致
  • Unlock() : 解除写锁定,与 Mutex 一致

Lock()实现逻辑

写操作所做的两件事:

  1. 获取互斥锁
  2. 阻塞等待所有读操作结束(如果有)

func (rw *RWMutex) Lock() 接口实现流程:

Unlock()实现逻辑

解除写操作所做的两件事:

  1. 唤醒因读锁定而被阻塞的协程(如果有)
  2. 解除互斥锁

func (rw *RWMutex) Unlock() 接口实现流程:

RLock()实现逻辑

读锁定所做的两件事:

  1. 增加读操作计数,即 readerCount++
  2. 阻塞等待写操作结束(如果有)

func (rw *RWMutex) RLock() 接口实现流程

RUnlock()实现逻辑

解除读锁定所做的两件事:

  1. 减少读操作计数,即 readerCount—
  2. 唤醒等待写操作的协程(如果有)

func (rw *RWMutex) RUnlock() 接口实现流程:

注意:只有最后一个解除读锁定的协程才会释放信号量,将协程阻塞等待写操作的协程唤醒。

使用场景

写操作阻止写操作

读写锁中包含一个互斥锁 Mutex,写操作必须先获取到此互斥锁,若此互斥锁已被其他协程获取,当前协程会阻塞等待该互斥锁。

写操作阻止读操作

RWMutex.readerCount 是个整型值,表示读者数量,不考虑写操作的情况下,每次读锁定会将该值+1,每次解除读锁定会将该值-1,所以readerCount 的取值为[0, N],N为读者个数,实际上最大可支持 2^30 个并发读者。

当写锁定进行时,会先将 readerCount 减去2^30,readerCount 就变成了负值,此时再有读锁定到来时检测到 readerCount 为负值,便知道有写操作在进行,只好阻塞等待。而真实的读操作个数并不会丢失,只需要将 readerCount 加上 2^30 即可获得。

所以,写操作将 readerCount 变成负值来阻止读操作的。

读操作阻止写操作

读锁定会先将 RWMutext.readerCount 加1,此时写操作到来时发现读者数量不为0,会阻塞等待所有读操作结束。

所以,读操作通过 readerCount 来将来阻止写操作的。

写锁定为什么不会被饿死

写操作要等待读操作结束后才可以获得锁,写操作等待期间可能还有新的读操作持续到来,如果写操作等待所有读操作结束,很可能被饿死。

然而,通过 RWMutex.readerWait 可完美解决这个问题。写操作到来时,会把 RWMutex.readerCount 值拷贝到 RWMutex.readerWait 中,用于标记排在写操作前面的读者个数。前面的读操作结束后,除了会递减 RWMutex.readerCount,还会递减 RWMutex.readerWait 值,当 RWMutex.readerWait 值变为0时唤醒写操作。

所以,如上图,前面的读操作结束后会唤醒写操作,写操作结束后会唤醒后面的读操作。


加我微信,拉你进技术交流群:wucs_dd

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-08-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 微客鸟窝 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 数据结构
  • 接口定义
    • Lock()实现逻辑
      • Unlock()实现逻辑
        • RLock()实现逻辑
          • RUnlock()实现逻辑
          • 使用场景
            • 写操作阻止写操作
              • 写操作阻止读操作
                • 读操作阻止写操作
                  • 写锁定为什么不会被饿死
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档