当前位置: 首页 > news >正文

简略说一下go的sync.RWMutex锁

在简略的说之前,首先要对RW锁的结构有一个大致的了解

type RWMutex struct {w           Mutex  // 写锁互斥锁,只锁写锁,和读锁无关writerSem   uint32 // sema锁--用于“写协程”排队等待readerSem   uint32 // sema锁--用于“读协程”排队等待readerCount int32  // 读锁的计数器readerWait  int32  // 等待读锁释放的数量
}

这里要额外说一句,writerSem和readerSem底层都是semaRoot,这个结构体有兴趣可以了解下,他的用法有点类似于一个简版的channel,很多地方把他的初始值设置为0,使得所有想获取该sema锁的协程都排队等待,也就是说初始值为0的sema锁,他本身起到的作用是成为一个协程等待队列,就像没有缓冲区的channel一样。

好现在进入正题。本文是为了在面试中能快速口述RW锁,并非为了完整解答RW锁的机制。

前提:

readerCount这个参数非常重要

  • 为负数时:说明此锁已经被写协程占据,所有渴望加读锁的协程被阻塞在readerSem
  • 为正数时:正数的数值为当前持有该锁的所有读协程的数量总和,所有渴望加写锁的协程被阻塞在writerSem

读写锁互斥性

  • 读锁是并发的,可以多个协程持有一把读锁。
  • 写锁是唯一的,互斥的,同一时刻只能有一个写协程拥有写锁
  • 读锁和写锁是互斥的,写锁生效时,是不能有读锁被获取到,同样,必须所有的读锁都被释放,或者压根没有读协程获取读锁,写锁方可被获取。

一个很重要的参数:const rwmutexMaxReaders = 1 << 30 ,rwmutexMaxReaders 非常大,意思是最多能有rwmutexMaxReaders(1 << 30  十进制为  4294967296)个协程同时持有读锁。


写锁上锁场景:

首先分析写锁,因为读锁的很多操作是根据写锁来的,如果一上来就说读锁,很多东西没法串起来

       

func (rw *RWMutex) Lock() {// race.Enabled是官方的一些测试,性能检测的东西,无需关心,这个只在编译阶段才能启用if race.Enabled {_ = rw.w.staterace.Disable()}// First, resolve competition with other writers.rw.w.Lock()// Announce to readers there is a pending writer.r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders// Wait for active readers.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))}
}
  1. 获取写锁--没有读锁等待

    1. rw.w.Lock进行加锁,阻塞后续的其他写协程的锁请求。
    2. atomic.AddInt32进行原子操作,减去rwmutexMaxReaders,减成功才说明没有并发问题,可以继续下面的操作。然后再加上rwmutexMaxReaders,得到真正的readerCount的数值。
    3. 此时还需要再次进行一个原子操作,把当前readerCount的值搬运到readerWait里面,意思是当前要获取写锁的协程需要等待的读锁的数量。
    4. 此时readerCount只有两种情况,一种是0,一种是正数,因为只有写锁上的时候才为负数,而上面的操作已经还原了加写锁之前的值,而w.Lock保证了不会有2个及以上的写协程去同时操作
    5. readerCount 如果是 0,加锁成功。
    6. 如果不为0则说明有读锁等待,详见场景2
  2. 获取写锁--有读锁等待

    1. 接上面的判断,如果readrCount不为0,说明前面有读锁正在运行,写锁需要等待所有读锁释放才能获取写锁,当前协程执行 runtime_SemacquireMutex 进入 waiterSem 的休眠队列等待被唤醒
  3. 获取写锁--前面已经有写锁了

    1. 后面的写协程也调用 rw.w.Lock() 进行加锁,因为前面有写锁已经获取了w,所以后续的写协程会因为获取不到w,而进入到w的sema队列里面,w是一个mutex的锁,mutex锁里是一个sema锁,sema锁因为没有设置初始值,所以退化为一个队列,而获取不到w锁的就会直接被阻塞在w的sema队列里,从而无法进行接下来的操作

写锁释放锁场景:

func (rw *RWMutex) Unlock() {if race.Enabled {_ = rw.w.staterace.Release(unsafe.Pointer(&rw.readerSem))race.Disable()}// Announce to readers there is no active writer.r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)if r >= rwmutexMaxReaders {race.Enable()throw("sync: Unlock of unlocked RWMutex")}// Unblock blocked readers, if any.for i := 0; i < int(r); i++ {runtime_Semrelease(&rw.readerSem, false, 0)}// Allow other writers to proceed.rw.w.Unlock()if race.Enabled {race.Enable()}
}
  1. 释放写锁--后面【没有】读锁等待

    1. 执行atomic.AddInt32进行原子操作,把已经为负值的readerCount还原为正数,此时已经算释放了写锁
    2. (此步骤不重要,就是个判错)如果还原后的readerCount比rwmutexMaxReaders还大,这就是说明出错了,直接throw弹出错误,throw这个方法是内部方法,对go来说就是panic了
    3. 此场景因为没有读锁等待,此时的readerCount为0,不会进入for循环,直接rw.w.Unlock释放w锁,允许其他写协程加锁,此时其他的写协程会被从w里的sema队列里唤醒
  2. 释放写锁--后面【有】读锁等待

    1. 接场景1,原子操作readerCount释放写锁后,如果r是大于0,说明有读锁等待,for循环readerSem里面所有的等待的读协程,因为读锁是共享锁,所以所有的读协程都会获取锁并被唤醒
    2. rw.w.Unlock释放w锁,允许其他写协程加锁,其他的写协程会被从w里的sema队列里唤醒
  3. 释放写锁--后面有【写锁】等待

    1. 上接场景2,当rw.w.Unlock释放w锁,其他的写协程会被从w里的sema队列里唤醒
    2. 写锁释放的时候,是先唤醒所有等待的读锁,再解除rw.w锁,所以,并不会造成读锁的饥饿
    3. 后面读锁再次对rw.w进行上锁,重复上面所述写锁获取锁的场景


读锁上锁场景:

func (rw *RWMutex) RLock() {// race.Enabled都是测试用的代码,在阅读源码的时候可以跳过if race.Enabled {_ = rw.w.staterace.Disable()}if atomic.AddInt32(&rw.readerCount, 1) < 0 {// A writer is pending, wait for it.runtime_SemacquireMutex(&rw.readerSem, false, 0)}if race.Enabled {race.Enable()race.Acquire(unsafe.Pointer(&rw.readerSem))}
}
  1. 获取读锁--此时没有写锁.

         最简单的场景,协程对rw.readerCount进行原子操作加一,如果得到的结果为正数,说明获取读锁成功。
  2. 获取读锁--前方已经有写锁抢占了该锁

    1.  当协程对rw.readerCount进行原子加1操作的时候,发现加完,readerCount还是负数,说明在这个时间点以前,已经有协程获取了写锁
    2.  runtime_SemacquireMutex 方法将当前协程加入readerSem队列,等待写锁释放后被批量唤醒(写锁释放会一次性放出所有的堆积的读协程)
  3. 获取读锁--前方有写锁抢已经被抢占,后方有写锁等待

          写锁在获取的时候,对RWMutex.w进行加锁,是独占锁,如果前方一个写锁已经得到了锁正在处理业务,那么后方的写锁进来就会发现加不上锁,直接在rw.w.lock阶段就阻塞了,后面的逻辑是无法继续运行的,所以进入不了writerSem,它只会进入到w这个mutex锁的sema队列里,读锁则进入休眠队列readerSem

读锁释放锁场景:

func (rw *RWMutex) RUnlock() {if race.Enabled {_ = rw.w.staterace.ReleaseMerge(unsafe.Pointer(&rw.writerSem))race.Disable()}if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {// Outlined slow-path to allow the fast-path to be inlinedrw.rUnlockSlow(r)}if race.Enabled {race.Enable()}
}
  1. 释放读锁--后方没有写锁等待

    1. atomic.AddInt32 进行原子操作,让readerCount 减1,操作后,如果readerCount 大于0,说明后方是没有写锁等待的,释放锁后整个流程就结束了
  2. 释放读锁--后方有写锁等待

    1. 原子操作eaderCount 减1后,发现eaderCount是小于0的,此时说明已经有等待写锁的协程在尝试获取写锁。执行 rw.rUnlockSlow(r) 。

                

func (rw *RWMutex) rUnlockSlow(r int32) {if r+1 == 0 || r+1 == -rwmutexMaxReaders {race.Enable()throw("sync: RUnlock of unlocked RWMutex")}// A writer is pending.if atomic.AddInt32(&rw.readerWait, -1) == 0 {// The last reader unblocks the writer.runtime_Semrelease(&rw.writerSem, false, 1)}
}

             这里是有个前提的,上面提到(详见上面的获取写锁的场景1),如果写协程进来想加写锁,需要把它需要等待的读锁数量从readerCount里赋值给readerWait。当它等待的读锁释放后,就需要用rUnlockSlow方法对readerWait进行减1,如果readWait == 0 ,说明这是最后一个需要等待的读锁也释放了,释放后就通知该写锁可以被唤醒了,锁给你了。

相关文章:

简略说一下go的sync.RWMutex锁

在简略的说之前&#xff0c;首先要对RW锁的结构有一个大致的了解 type RWMutex struct {w Mutex // 写锁互斥锁&#xff0c;只锁写锁&#xff0c;和读锁无关writerSem uint32 // sema锁--用于“写协程”排队等待readerSem uint32 // sema锁--用于“读协程”排队…...

软考马上要报名了,出现这些问题怎么办?

目前&#xff0c;四川、山东、山西、辽宁、河北等地已经率先发布了2023年上半年软考报名通知。 四川&#xff1a;2023年3月13日-4月4日 山东&#xff1a;2023年3月17日9:00-4月3日16:00 山西&#xff1a;2023年3月14日9:00-3月28日11:00 辽宁&#xff1a;2023年3月14日8:30…...

单链表(增删查改)

目录一、什么是单链表&#xff1f;二、单链表的增删查改2.1 结构体变量的声明2.2 申请新结点2.2 链表的头插2.3 链表的尾插2.4 链表的头删2.5 链表的尾删2.6 链表的查找2.7 链表的任意位置后面插入2.8 链表的任意位置后面删除2.9 链表的销毁2.10 链表的打印三、代码汇总3.1 SLi…...

端口复用(bind error: Address already in use 问题)

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起探讨和分享Linux C/C/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。 端口复用专栏&#xff1a;《Linux从小白到大神》《网络编程》 在前面讲解TCP状态转换中提到过一个2MSL…...

数字化引领乡村振兴,VR全景助力数字乡村建设

一、数字乡村建设加速经济发展随着数字化建设的推进&#xff0c;数字化农业产业正在成为农业产业发展的主导力量&#xff0c;因此数字化技术赋予农业产业竞争力的能力不可小觑。数字化乡村建设背景下&#xff0c;数字化信息技术将全面改造升级农村产业&#xff0c;从农业、养殖…...

【数据结构入门】-链表之双向循环链表

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【数据结构初阶&#xff08;C实现&#xff09;】 文章目录链表初始化打印链表尾插尾删新建一个节点头插头删查找在pos之前插入*删除pos位…...

Jenkins自动化部署入门

Jenkins自动化部署入门 一、简介 Jenkins是一个开源软件项目&#xff0c;是基于Java开发的一种持续集成工具&#xff0c;用于监控持续重复的工作&#xff0c;旨在提供一个开放易用的软件平台&#xff0c;使软件的持续集成变成可能。 Jenkins自动化部署实现原理 二、Jenkins部…...

Springboot 读取模板excel信息内容并发送邮件, 并不是你想想中的那么简单

Springboot 读取模板excel信息内容并发送邮件 背景技术选型搭建过程数据加密隐藏问题暴露背景追溯解决背景 在我们日常开发中, 会遇到这样一种场景, 就是读取表格中的数据, 并将数据以附件的形式通过邮箱发送到表格中的每个人 即: excel 读取 excel 写入 发送邮件(携带附件), 例…...

蓝桥杯真题31日冲刺 |第一天

蓝桥杯真题31日冲刺 |第一天 一&#xff1a;完全平方数 题目&#xff1a;[链接](完全平方数 - 蓝桥云课 (lanqiao.cn)) 思路&#xff1a; 将 每个 完全平方数都 消掉&#xff0c;剩下的就是 不能构成平方的数 以12 为例&#xff1a; 所以 12 只要再 乘个三 即可满足 代…...

STM32开发(18)----CubeMX配置RTC

CubeMX配置RTC前言一、什么是RTC&#xff1f;RTC时钟源RTC备份域二、实验过程1.CubeMX配置2.代码实现3.实验结果总结前言 本章介绍使用STM32CubeMX对RTC进行配置的方法&#xff0c;RTC的原理、概念和特点&#xff0c;配置各个步骤的功能&#xff0c;并通过实验方式验证。 一、…...

Qt 单例模式第一次尝试

文章目录摘要单例模式如何使用Qt 的属性系统总结关键字&#xff1a; Qt、 单例、 的、 Q_GLOBAL_STATIC、 女神节摘要 世界上第一位电脑程序设计师是名女性&#xff1a;Ada Lovelace (1815-1852)是一位英国数学家兼作家&#xff0c;她是第一位主张计算机不只可以用来算数的人…...

C语言--一维数组

数组概念 数组&#xff1a;是一种构造数据类型&#xff0c;用以处理批量的同种类型的数据。 主要特点&#xff1a;数据量大 &#xff0c;类型相同 一维数组的定义 语法&#xff1a; 类型说明符 数组名[整型常量表达式]&#xff1b; 注意&#xff1a; 方括号里面的内容用于指…...

DataGear 4.5.1 发布,数据可视化分析平台

DataGear 4.5.1 发布&#xff0c;严重 BUG 修复&#xff0c;具体更新内容如下&#xff1a; 修复&#xff1a;修复SQL数据集对于DB2、SQLite等数据源预览时会报错的BUG&#xff1b;修复&#xff1a;修复系统对于MySQL、MariaDB等数据源中无符号数值类型有时报错的BUG&#xff1…...

Springboot——@valid 做字段校验和自定义注解

文章目录前言注意实现测试环境验证自带的注解自定义valid注解自定义注解和处理类创建参数接收类&#xff0c;并增加字段注解接口中使用自测环节正常测试异常测试自定义全局异常监听扩展递归参数下valid不识别的坑前言 再项目开发中&#xff0c;针对前端传递的参数信息&#xf…...

c语言基础练习题详解

&#x1f49e;&#x1f49e; 1.C语言程序的基本单位是&#xff08;C&#xff09;。 A&#xff0e;程序行 B&#xff0e; 语句 C&#xff0e; 函数 D&#xff0e;字符 &#x1f49e;&#x1f49e; 2.已知各变量的类型说明如下&#xff1a; int m6,n,a,b; unsigned long w8;…...

C语言设计模式:实现简单工厂模式和工程创建

目录 一&#xff0c;设计模式概念引入 ① 什么是设计模式 ② 什么是类和对象 ③ 什么是工厂模式 二&#xff0c;C语言工厂模式的实现 ① 普通类和对象的代码实现 ② 工厂模式代码实现 ● cat.c ● dog.c ● person.c ● animal.h ● mainpro.c ● 完善mainpro.c …...

3.6日报

今天进行3.0信号整理工作 做官网后台技术文档 了解grpc gRPC是rpc框架中的一种&#xff0c;是rpc中的大哥 是一个高性能&#xff0c;开源和通用的RPC框架&#xff0c;基于Protobuf序列化协议开发&#xff0c;且支持众多开发语言。 面向服务端和协议端&#xff0c;基于http…...

中文代码88

PK 嘚釦 docProps/PK 嘚釦|,g z docProps/app.xml漅AN??駠(髂v诖m岼侸 魣,g踃$秂D廋Qvf漶x莗笳w?:瘜^?俍欶辇2}?睧汎 t#:?效7治XtA鏊?羄鈋嫿饄攗Tv契"D桷撵vJ鉂?闌 Jg??浱?樱沲gic鋹峡?sū窛葻?]迾?9卑{艏 rk\?洺萹啰N?W??2&quo…...

ElasticSearch 基础(五)之 映射

目录前言一、映射&#xff08;Mapping&#xff09;简介二、动态映射&#xff08;Dynamic mapping&#xff09;1、动态字段映射1.1、日期检测1.1.1、禁用日期检测1.1.2、自定义检测到的日期格式1.2、数值检测2、动态模板三、显示映射&#xff08;Explicit mapping&#xff09;1、…...

【C语言督学训练营 第二天】C语言中的数据类型及标准输入输出

文章目录一、前言二、数据类型1.基本数据类型①.整形②.浮点型③.字符型2.高级数据类型3.数据分类①.常量②.变量三、标准输入输出1.scanf2.printf四、进制转换1.进制转换简介2.十进制转其他进制3.其他进制转换五、OJ网站的使用一、前言 王道2024考研408C语言督学营第二天&…...

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施&#xff0c;由雇主和个人按一定比例缴纳保险费&#xff0c;建立社会医疗保险基金&#xff0c;支付雇员医疗费用的一种医疗保险制度&#xff0c; 它是促进社会文明和进步的…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

【单片机期末】单片机系统设计

主要内容&#xff1a;系统状态机&#xff0c;系统时基&#xff0c;系统需求分析&#xff0c;系统构建&#xff0c;系统状态流图 一、题目要求 二、绘制系统状态流图 题目&#xff1a;根据上述描述绘制系统状态流图&#xff0c;注明状态转移条件及方向。 三、利用定时器产生时…...

ardupilot 开发环境eclipse 中import 缺少C++

目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

重启Eureka集群中的节点,对已经注册的服务有什么影响

先看答案&#xff0c;如果正确地操作&#xff0c;重启Eureka集群中的节点&#xff0c;对已经注册的服务影响非常小&#xff0c;甚至可以做到无感知。 但如果操作不当&#xff0c;可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

Yolov8 目标检测蒸馏学习记录

yolov8系列模型蒸馏基本流程&#xff0c;代码下载&#xff1a;这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中&#xff0c;**知识蒸馏&#xff08;Knowledge Distillation&#xff09;**被广泛应用&#xff0c;作为提升模型…...