逐步学习Go-sync.RWMutex(读写锁)-深入理解与实战

概述

在并发编程中,我们经常会遇到多个线程或协程访问共享资源的情况。为了保护这些资源不被同时修改,我们会用到"锁"的概念。

Go中提供了读写锁:sync.RWMutex。
sync.RWMutex是Go语言提供的一个基础同步原语,它是Reader/Writer Mutual Exclusion Lock的缩写,通常被称为"读写锁"。
读写锁允许多个读锁同时拥有者,但在任何时间点只允许一个写锁拥有者,或者没有锁拥有者。

这让读多写少的场景获得了更高的并发性能。

应用场景

  1. 典型应用场景就是读多写少
  2. 一写多读

提供的方法

sync.RWMutex提供了以下方法:

type RWMutex
// 获取写锁,有读锁或者写锁被其他goroutine使用则阻塞等待
func (rw *RWMutex) Lock()
// 尝试获取写锁,获取到则返回true,没有获取到则为false
func (rw *RWMutex) TryLock() bool
// 释放写锁
func (rw *RWMutex) Unlock()
// 获取读锁,
func (rw *RWMutex) RLock()
// 尝试获取读锁,获取到则返回true,没有获取到则为false
func (rw *RWMutex) TryRLock() bool
// 释放读锁
func (rw *RWMutex) RUnlock()// 返回Locker
func (rw *RWMutex) RLocker() Locker

COPY

注意

使用RWMutex的时候,一旦调用了Lock方法,就不能再把该锁复制到其他地方使用,否则可能会出现各种问题。这是由于锁的状态(被哪个协程持有,是否已经被锁定等)是存储在RWMutex的结构体中,如果复制了RWMutex,那么复制后的RWMutex就会有一个全新的状态,锁的行为就会变得不可预测。
RWMutex和Mutex一样,一旦有了Lock调用就不能到处copy了,否则出现各种问题。

源码实现

RWMutex结构体

让我们一起深入Go的源码,看看RWMutex是如何实现的。
RWMutex 的结构体主要包括五个主要的字段,这些字段描述了锁的当前状态和持有者信息:

type RWMutex struct {// Mutex,互斥锁。写者互斥锁,所有的写者加锁都调用w.Lock或者w.TryLockw           Mutex   // 写者信号量。当最后一个读者释放了锁,会触发一个信号通知writerSemwriterSem   uint32  // 读者信号量。当写者释放了锁,会触发一个信号通知readerSemreaderSem   uint32      // readerCount 记录当前持有读锁的协程数量。如果为负数,表示有写者在等待所有读者释放锁。如果为0,表示没有任何协程持有锁readerCount atomic.Int32 // readerWait 记录写者需要等待的读者数量。当一个写者获取了锁之后,readerWait会设置为当前readerCount的值。当读者释放锁时,readerWait会递减1readerWait  atomic.Int32 
}

COPY

读者加锁RLock()

加读锁时非常简单,就是将结构体中的readerCount加1,如果+1后为负数表示有写者等待则等待写者执行完成。

实现代码

func (rw *RWMutex) RLock() {// 读者数量+1if rw.readerCount.Add(1) < 0 {// 加1以后如果readerCount是负数表示有写者持有了互斥锁// 读者等待信号量释放// 此时读锁已经加上了,等待写者释放信号量就可以了runtime_SemacquireRWMutexR(&rw.readerSem, false, 0)}
}

COPY

读者RTryLock()

这个函数是RWMutex中的TryRLock方法,它试图以非阻塞的方式获取读锁。让我们一步一步地看它是如何工作的。
先看图:

a771641e0c3056616025f39cfa7076b2.png

实现代码


func (rw *RWMutex) TryRLock() bool {for {// 查看当前读者数量c := rw.readerCount.Load()if c < 0 {// 小于0表示有写者已经Penging,加锁失败return false}// 读者数量+1,加读锁成功if rw.readerCount.CompareAndSwap(c, c+1) {return true}}
}

COPY

读者释放读锁RUnlock()

RUnlock方法用于释放读锁。 当一个读者完成读操作并想要释放锁时,就可以调用这个方法。

1af56f016db14721529ee7dd77dfb6bb.png

实现代码


func (rw *RWMutex) RUnlock() {// 释放锁就是-1,// 如果readerCount小于0表示有写者Pending// 进入rUnlockSlowif r := rw.readerCount.Add(-1); r < 0 {rw.rUnlockSlow(r)}
}func (rw *RWMutex) rUnlockSlow(r int32) {// 边界问题处理// r+1 ==0 表示没有读者加锁,却调用了释放读锁// r+1 == -rwmutexMaxReaders表示没有读者加锁,有写者持有互斥锁却释放读锁if r+1 == 0 || r+1 == -rwmutexMaxReaders {race.Enable()fatal("sync: RUnlock of unlocked RWMutex")}// 这表示这是最后一个读者了,最后一个读者要发送信号量通知写者不用等了if rw.readerWait.Add(-1) == 0 {// The last reader unblocks the writer.runtime_Semrelease(&rw.writerSem, false, 1)}
}

COPY

写者加锁Lock()

实现代码


const rwmutexMaxReaders = 1 << 30func (rw *RWMutex) Lock() {// 先持有互斥锁,已经有其他写者持有了互斥锁则等待rw.w.Lock()// rw.readerCount.Add(-rwmutexMaxReaders)这个表示先将readerCount设置为负数表示有写者在等待// 再+rwmutexMaxReaders是为了求出当前reader的数量r := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders// 将当前reader的数量加到readerWait表示要等待的读者完成的个数if r != 0 && rw.readerWait.Add(r) != 0 {// 阻塞等待万有的读者完成释放信号量了runtime_SemacquireRWMutex(&rw.writerSem, false, 0)}
}

COPY

写者加锁TryLock()

实现代码


func (rw *RWMutex) TryLock() bool {// 调用互斥锁的TryLock,互斥锁TryLock返回false这儿也直接返回falseif !rw.w.TryLock() {return false}// 加锁成功后// 如果当前还有写者,CompareAndSwap就返回失败if !rw.readerCount.CompareAndSwap(0, -rwmutexMaxReaders) {// 返回失败就释放互斥锁rw.w.Unlock()// 加锁失败return false}// 加锁成功return true
}

COPY

写者解锁Unlock()

实现代码


func (rw *RWMutex) Unlock() {// 这里是对Lock readerCount的逆向操作// 在Lock的时候对readerCount减去了rwmutexMaxReaders,这次加回来;这样就还原了readerCount,即使在Lock之后依然有读者加锁r := rw.readerCount.Add(rwmutexMaxReaders)if r >= rwmutexMaxReaders {race.Enable()fatal("sync: Unlock of unlocked RWMutex")}// 然后循环看当前有多少读者正在等待信号,就释放多少次心血号for i := 0; i < int(r); i++ {runtime_Semrelease(&rw.readerSem, false, 0)}// Allow other writers to proceed.rw.w.Unlock()
}

COPY

测试


package mutex_testimport ("sync""testing""time""github.com/stretchr/testify/assert"
)// 测试读写互斥锁在正常读锁定和解锁情况下的成功执行
func TestRWMutex_ShouldSuccess_WhenNormalReaderLockAndUnLock(t *testing.T) {// 初始化一个读写互斥锁rwmutex := sync.RWMutex{}// 获取读锁rwmutex.RLock()// 设置成功标志为true,使用defer确保在函数结束时释放读锁isSuccess := truedefer rwmutex.RUnlock()// 记录日志表示测试成功t.Log("success")// 断言成功标志为trueassert.True(t, isSuccess)
}// 测试RWMutex的写锁功能是否正常
func TestRWMutex_ShouldSuccess_WhenNormalWriterLockAndUnLock(t *testing.T) {rwmutex := sync.RWMutex{} // 创建一个sync.RWMutex类型的变量rwmutex.Lock()            // 获取写锁isSuccess := true         // 标记为成功状态defer rwmutex.Unlock()    // 确保在函数退出时释放锁,避免死锁t.Log("success")          // 记录测试日志assert.True(t, isSuccess) // 断言isSuccess为true,验证操作成功
}// 函数测试了在正常情况下,
// 读写锁(RWMutex)的读锁(RLock)和写锁(Lock)的加锁与解锁操作是否成功。
func TestRWMutex_ShouldSuccess_WhenNormalReaderWriterLockAndUnLock(t *testing.T) {// 初始化一个读写锁rwmutex := sync.RWMutex{}// 尝试获取读锁并立即释放rwmutex.RLock()rwmutex.RUnlock()// 尝试获取写锁并立即释放rwmutex.Lock()rwmutex.Unlock()// 标记测试为成功isSuccess := true// 记录测试成功日志t.Log("success")// 断言测试结果为真assert.True(t, isSuccess)
}// 测试读写锁在多协程情况下的读写互斥
func TestRWMutex_ShouldSuccess_WhenReaderAndWriterInDifferentRoutine(t *testing.T) {// 初始化一个读写锁和等待组,用于协调不同协程的操作。rwmutex := sync.RWMutex{}wg := sync.WaitGroup{}wg.Add(2) // 预期有两个协程完成操作// 启动一个协程作为读锁持有者go func() {rwmutex.RLock()   // 获取读锁println("reader") // 打印读操作标识rwmutex.RUnlock() // 释放读锁wg.Done()         // 表示读操作完成}()// 启动另一个协程作为写锁持有者go func() {rwmutex.Lock()    // 获取写锁println("writer") // 打印写操作标识rwmutex.Unlock()  // 释放写锁wg.Done()         // 表示写操作完成}()wg.Wait() // 等待所有协程完成操作isSuccess := truet.Log("success")          // 记录测试成功assert.True(t, isSuccess) // 断言测试结果为真
}// 测试读写锁在多个读锁情况下的读写互斥
func TestRWMutex_ShouldBlockWriter_WhenMultipleReader(t *testing.T) {rwmutex := sync.RWMutex{}ch := make(chan bool)wg := sync.WaitGroup{}wg.Add(2)for i := 0; i < 2; i++ {go func(i int) {wg.Done()rwmutex.RLock()println("reader Locked", i)time.Sleep(10 * time.Second)rwmutex.RUnlock()println("reader UnLocked", i)}(i)}go func() {wg.Wait()println("writer try to accquire wlock")rwmutex.Lock()println("writer has accquired wlock")defer rwmutex.Unlock()ch <- true}()<-chisSuccess := truet.Log("success")assert.True(t, isSuccess)
}// 测试读写锁在多个写锁情况下的读写互斥
func TestRWMutex_ShouldBlockReaders_WhenWriterIsPresent(t *testing.T) {rwmutex := sync.RWMutex{}wg := sync.WaitGroup{}wg.Add(1)go func() {println("writer try to accquire wlock")rwmutex.Lock()println("writer has accquired wlock")wg.Done()time.Sleep(10 * time.Second)defer rwmutex.Unlock()println("writer has released wlock")}()wg.Wait()wg.Add(2)for i := 0; i < 2; i++ {go func(i int) {println("reader try to lock", i)rwmutex.RLock()println("reader Locked", i)rwmutex.RUnlock()println("reader UnLocked", i)wg.Done()}(i)}wg.Wait()isSuccess := truet.Log("success")assert.True(t, isSuccess)
}// 测试读写锁在多个写锁情况下的读写互斥
func TestRWMutex_ShouldBlockConcurrentWriters(t *testing.T) {rwmutex := sync.RWMutex{}var blockedWriter boolch := make(chan bool)wg := sync.WaitGroup{}wg.Add(1)go func() {wg.Done()println("Writer 1 try to accquire wlock")rwmutex.Lock()println("Writer 1 has accquired wlock")defer rwmutex.Unlock()time.Sleep(15 * time.Second)}()go func() {wg.Wait()println("Writer 2 try to accquire wlock")rwmutex.Lock()println("Writer 2 has accquired wlock")ch <- truedefer rwmutex.Unlock()}()select {case <-ch:blockedWriter = falsecase <-time.After(20 * time.Second):blockedWriter = true}assert.True(t, blockedWriter)
}// 测试读写锁在多个读锁情况下的读写互斥
func TestRWMutex_ShouldLockSuccess_WhenTryingToReadLockTwice(t *testing.T) {rwmutex := sync.RWMutex{}writerWaitGroup := sync.WaitGroup{}writerWaitGroup.Add(1)go func() {rwmutex.RLock()println("readlock locked once")rwmutex.RLock()println("readlock locked twice")rwmutex.RUnlock()rwmutex.RUnlock()defer writerWaitGroup.Done()}()writerWaitGroup.Wait()isSuccess := trueassert.True(t, isSuccess)
}// 测试读写锁在多个写锁情况下的读写互斥
func TestRWMutex_ShouldBeBlocked_WhenTryingToWriteLockTwice(t *testing.T) {rwmutex := sync.RWMutex{}ch := make(chan bool)go func() {rwmutex.Lock()println("writelock locked once")rwmutex.Lock()println("writelock locked twice")rwmutex.Unlock()rwmutex.Unlock()ch <- true}()isBlocked := falseselect {case <-ch:println("should not execute this block")assert.False(t, isBlocked)case <-time.After(10 * time.Second):isSuccess := trueprintln("executed timeout block")assert.True(t, isSuccess)}}// 测试读写锁在多个读锁情况下的读写互斥
func TestRWMutex_ShouldBeBlocked_WhenAccquireWriteLockThenReadLock(t *testing.T) {rwmutex := sync.RWMutex{}ch := make(chan bool)go func() {rwmutex.Lock()println("writelock locked once")rwmutex.RLock()println("readlock locked twice")rwmutex.RUnlock()rwmutex.Unlock()ch <- true}()isBlocked := falseselect {case <-ch:println("should not execute this block")assert.False(t, isBlocked)case <-time.After(10 * time.Second):isSuccess := trueprintln("executed timeout block")assert.True(t, isSuccess)}}// 测试读写锁在多个读锁情况下的读写互斥
func TestRWMutex_ShouldBeBlocked_WhenAccquireReadLockThenWriteLock(t *testing.T) {rwmutex := sync.RWMutex{}ch := make(chan bool)go func() {rwmutex.RLock()println("readlock locked once")rwmutex.Lock()println("writelock locked twice")rwmutex.Unlock()rwmutex.RUnlock()ch <- true}()isBlocked := falseselect {case <-ch:println("should not execute this block")assert.False(t, isBlocked)case <-time.After(10 * time.Second):isSuccess := trueprintln("executed timeout block")assert.True(t, isSuccess)}}// 测试读写锁在多个读锁情况下的读写互斥
func TestRWMutex_ShouldDeadlockOrBlocked_WhenLockOneGoroutineAccquiredLockAndAnotherGoroutineAccquireLockAgain(t *testing.T) {var rwmutex1, rwmutex2 sync.RWMutexwg := sync.WaitGroup{}wg1 := sync.WaitGroup{}ch := make(chan bool)wg.Add(1)wg1.Add(1)go func() {rwmutex1.Lock()println("rwmutex1 locked")wg.Done()wg1.Wait()println("rwmutex2 try to accquire lock")rwmutex2.Lock()}()go func() {wg.Wait()rwmutex2.Lock()println("rwmutex2 locked")wg1.Done()println("rwmutex1 try to accquire lock")rwmutex1.Lock()ch <- true}()isBlocked := falseselect {case <-ch:println("should not execute this block")assert.False(t, isBlocked)case <-time.After(10 * time.Second):isSuccess := trueprintln("executed timeout block")assert.True(t, isSuccess)}}

参考

 逐步学习Go-sync.RWMutex(读写锁)-深入理解与实战 – 小厂程序员

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/308388.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Nikon | NEF格式图片批量转换为jpg格式

如何将nikon相机拍的NEF格式图片转换为jpg格式呢&#xff1f; 这里推荐一个在线转换的网址&#xff1a; https://picflow.com/convert/nef-to-jpg 添加图片后&#xff0c;可以批量选择图片&#xff0c;点击转换后即可进行下载

74HC595引脚图时序图工作原理

74HC595和74hc164一样是在单片机系统中常用的芯片之一他的作用就是把串行的信号转为并行的信号&#xff0c;常用在各种数码管以及点阵屏的驱动芯片&#xff0c; 使用74HC595可以节约单片机mcu的io口资源&#xff0c;用3个io就可以控制8个数码管的引脚&#xff0c;他还具有一定的…

[攻防世界]Reversing-x64Elf-100

1.查壳 无壳&#xff0c;ELF文件 2.用IDA64打开 找到关键部分 这里有坑&#xff0c;看清楚v3是长度为3数组&#xff0c;里面放三个字符串 3.脚本解密 v1"Dufhbmf" v2"pGimos" v3"ewUglpt" v4[v1,v2,v3] a1[0,0,0,0,0,0,0,0,0,0,0,0] for i …

乐趣Python——办公魔法:Word文件自动化

嘿&#xff0c;朋友们&#xff01;在这个办公小课堂中&#xff0c;我将为大家揭开一个神奇的秘密&#xff1a;Word文件自动化处理&#xff01; 通过这种魔法般的方式&#xff0c;我们可以大大提高办公效率&#xff0c;减少重复性工作。而Python作为我们的助手&#xff0c;将展现…

JavaSE-12笔记【集合1(+2024新)】

文章目录 1. 集合概述2.Collection2.1 Collection继承结构&#xff08;基于Java21&#xff09;2.2 Collection接口的常用方法2.3 Collection的遍历&#xff08;集合的通用遍历方式&#xff09;2.4 所有的有序集合都实现了SequencedCollection接口2.5 泛型2.5.1 如何判断是否可以…

防止狗上沙发,写一个浏览器实时识别目标检测功能

家里有一条狗&#x1f436;&#xff0c;很喜欢乘人不备睡沙发&#x1f6cb;️&#xff0c;恰好最近刚搬家 狗迎来了掉毛期 不想让沙发上很多毛。所以希望能识别到狗&#xff0c;然后播放“gun 下去”的音频&#x1f4e3;。 需求分析 需要一个摄像头&#x1f4f7; 利用 chrome…

Mapmost Alpha:开启三维城市场景创作新纪元

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

vue3中使用antv-S2表格(基础功能版)

先看展示效果&#xff1a; 可以调整行宽、列宽、自定义字段图标、表头图标、添加排序、显示总计、小计等 首先确保搭建一个vue3项目环境&#xff0c;从0开始的小伙伴着重看第一点&#xff1a; 一、搭建vue3项目环境 首先创建一个vue3vitets项目&#xff0c;可以查看下面相关…

头歌-机器学习实验 第8次实验 决策树

第1关&#xff1a;什么是决策树 任务描述 本关任务&#xff1a;根据本节课所学知识完成本关所设置的选择题。 相关知识 为了完成本关任务&#xff0c;你需要掌握决策树的相关基础知识。 引例 在炎热的夏天&#xff0c;没有什么比冰镇后的西瓜更能令人感到心旷神怡的了。现…

Spark-Scala语言实战(16)

在之前的文章中&#xff0c;我们学习了三道任务&#xff0c;运用之前学到的方法。想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&#xff0c;请留下你宝贵的点赞&#xff0c;谢谢。 Spark-Scala语言实战&#x…

使用doop识别近commons text漏洞的污点信息流

一、doop静态分析框架简介 1. doop静态分析框架简介 doop静态分析框架由希腊雅典大学plast-lab Yannis Smaragdakis团队设计开发&#xff0c;目前看是一款开源领域的比较先进的程序静态分析框架&#xff0c;一些程序静态分析论文的理论也有通过doop的规则实现后实验。 doop整…

c 解数独(通用方法,适用于9×9 数独)

折腾了一周时间&#xff0c;终于搞定99数独通用方法 思路&#xff1a;1.生成每行空位的值&#xff0c;也就是1-9中除去非0的数。 2.用行&#xff0c;列&#xff0c;宫判断每行中每个空位的最小取值范围后再重新生成每行。 3.随机提取生成的9行&#xff0c;判断每列之和是否等…

【数据结构与算法】:二叉树经典OJ

目录 1. 二叉树的前序遍历 (中&#xff0c;后序类似)2. 二叉树的最大深度3. 平衡二叉树4. 二叉树遍历 1. 二叉树的前序遍历 (中&#xff0c;后序类似) 这道题的意思是对二叉树进行前序遍历&#xff0c;把每个结点的值都存入一个数组中&#xff0c;并且返回这个数组。 思路&…

解决 IDEA每次打开新的项目都要重新设置maven问题

目录 一、当前项目设置maven 如下图&#xff1a; 二、设置打开的新项目的maven 如下图&#xff1a;​ 一、当前项目设置maven 对于当前项目我们都知道设置maven的配置要在 File -- Settings -- Build -- Maven 中设置 如下图&#xff1a; 二、设置打开的新项目的maven F…

150个 HTML5 网站模版 量大慢选

HTML5 网站模版 No.1 HTML5 网站模版 No.1

linux下安装nacos2.2.0

1、获取下载地址并下载 1.1、打开nacos官网 1.2、找到对应版本&#xff0c;点进去 ## 1.3、复制地址 1.4下载 # 进入要安装的目录&#xff0c;cd /usr/local/src # 执行wget https://github.com/alibaba/nacos/releases/download/2.2.0/nacos-server-2.2.0.tar.gz2、 安装…

RTSP/Onvif安防视频EasyNVR平台 vs.多协议接入视频汇聚EasyCVR平台:设备分组的区别

EasyNVR安防视频云平台是旭帆科技TSINGSEE青犀旗下支持RTSP/Onvif协议接入的安防监控流媒体视频云平台。平台具备视频实时监控直播、云端录像、云存储、录像检索与回看、告警等视频能力&#xff0c;能对接入的视频流进行处理与多端分发&#xff0c;包括RTSP、RTMP、HTTP-FLV、W…

架构设计参考项目系列主题:新零售SaaS架构:客户管理系统架构设计

什么是客户管理系统? 客户管理系统,也称为CRM(Customer Relationship Management),主要目标是建立、发展和维护好客户关系。 CRM系统围绕客户全生命周期的管理,吸引和留存客户,实现缩短销售周期、降低销售成本、增加销售收入的目的,从而提高企业的盈利能力和竞争力。 …

【ARM 裸机】汇编 led 驱动之原理分析

1、我们为什么要学习汇编&#xff1f;&#xff1f;&#xff1f; 之前我们或许接触过 STM32 以及其他的 32 位的 MCU ,都是基于 C 语言环境进行编程的&#xff0c;都没怎么注意汇编&#xff0c;是因为 ST 公司早已将启动文件写好了&#xff0c;新建一个 STM32 工程的时候&#…

nexus搭建maven与docker镜像的私有仓库

引言 通过nexus搭建maven与docker镜像的私有仓库,实现jar包与镜像动态更新、共享、存储。 一、nexus部署 通过docker-compose部署nexus name: java services:#############################环境#############################env-nexus:restart: always## 3.58.1image: so…