互斥锁Mutex

type Mutex struct {// 表示互斥锁状态state int32// 表示信号量,协程阻塞等待该信号量,解锁的协程释放信号量从而唤醒等待信号量的协程sema  uint32
}

  • Locked: 表示该Mutex是否已被锁定,0:没有锁定 1:已被锁定

  • Woken: 表示是否有协程已被唤醒,0:没有协程唤醒 1:已有协程唤醒,正在加锁过程中

  • Starving:表示该Mutex是否处于饥饿状态,0:没有饥饿 1:饥饿状态,说明有协程阻塞了超过1ms

  • Waiter: 表示阻塞等待锁的协程个数,协程解锁时根据此值来判断是否需要释放信号量

简单加锁

判断Locked标志位是否为0,如果是0则把Locked置1,代表加锁成功

加锁被阻塞

加锁前,如果Locked为1,则将Waiter+1,表示此协程阻塞等待,直到Locked为0后被唤醒

简单解锁

如果没有其他协程阻塞等待加锁,Waiter为0,则直接把Locked置0,

解锁并唤醒协程

如果Waiter大于0,有其他协程阻塞等待加锁,则当前协程解锁:将Locked置0;释放一个信号量,唤醒一个阻塞协程

自旋过程

自旋相当于CPU空转,sleep一小段时间。自旋过程当前协程会持续探测Locked是否改为0,如果改为0则可以直接运行,不必进入阻塞状态。可以充分利用CPU,避免协程切换

问题:如果加锁的协程特别多,每次都通过自旋获得锁,那么之前被阻塞的进程将很难获得锁,从而进入饥饿状态。1.8版本以来增加了一个状态,即Mutex的Starving状态。这个状态下不会自旋,一旦有协程释放锁,那么一定会唤醒一个协程并成功加锁

模式
normal模式

默认模式,该模式下,协程如果加锁不成功不会立即转入阻塞排队,而是判断是否满足自旋的条件,如果满足则会启动自旋过程,尝试抢锁

starvation模式

处于饥饿模式下,不会启动自旋过程,也即一旦有协程释放了锁,那么一定会唤醒协程,被唤醒的协程将会成功获取锁,同时也会把等待计数减1

读写锁RWMutex

读读不互斥,读写互斥,写写互斥

  • P操作 信号量值 -1,如果小于0就阻塞等待;V操作 信号量值 +1,唤醒阻塞的线程

  • 读者和写者加锁时,进行P操作;读者和写者释放锁时,进行V操作

  • 写者加锁和读者释放锁,操作writerSem;读者加锁和写者释放锁,操作readerSem

type RWMutex struct {// 控制写锁,获得写锁首先要获取该锁,实现了写写互斥w           Mutex  // 写阻塞等待的信号量,最后一个读者释放锁时会释放此信号量,通知写者进行写操作writerSem   uint32 // 读阻塞等待的信号量,持有写锁的协程释放锁后会释放此信号量,通知读者进行读操作readerSem   uint32 // 实现了读写互斥,读读不互斥// 只要有读操作到来,则此字段+1(读读不互斥)// 1.记录当前持有读锁(正在读或者等待写完再读)的协程数量  2.<0代表有写者在等(读写互斥)readerCount int32  // 记录写阻塞时需要等待多少个读者释放读锁(实现写操作不会被饿死)readerWait  int32  
}
源码
写者加锁Lock()
const rwmutexMaxReaders = 1 << 30func (rw *RWMutex) Lock() {// 先获取互斥锁rw.w.Lock()// 先将readerCount加一个很大的负数使其<0(表示当前有写者正在等或执行),然后用r记录原来的readerCountr := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders// r!=0说明当前还有读者正在读,随后让readerWait加上r(readerCount),表示要等待的读者完成的个数if r != 0 && rw.readerWait.Add(r) != 0 {// runtime_SemacquireRWMutex()阻塞当前goroutine,直到某个条件满足释放信号量的操作// 当前写者需要阻塞(writerSem信号量为0则阻塞),直到所有读者释放锁(信号量大于0则执行)runtime_SemacquireRWMutex(&rw.writerSem, false, 0)}
}
  • 先获取写锁

  • 将readerCount加一个很大的负数使其<0,表示当前有写者正在等待或执行

  • 将readerWait加上原本的readerCount,表示当前写操作之前要等待的读操作数量

  • 如果readerCount和readerWait都不为0,则等最后一个读操作执行完毕释放writerSem信号量,再执行写操作

读者加锁RLock()
func (rw *RWMutex) RLock() {// 读者数量+1(先加上读锁),然后判断如果小于0表示有写者持有写锁,则不能读者立即加锁if rw.readerCount.Add(1) < 0 {// 读者等待写者信号量释放runtime_SemacquireRWMutexR(&rw.readerSem, false, 0)}
}
  • 先readerCount+1,将读操作入队排队

  • 等写操作执行完(释放readerSem信号量),再执行读操作

写者释放锁UnLock()
 func (rw *RWMutex) Unlock() {// 在Lock加锁的时候对readerCount减去了rwmutexMaxReaders,这时加回来还原恢复r := rw.readerCount.Add(rwmutexMaxReaders)// 加rwmutexMaxReaders之前,readerCount减去了rwmutexMaxReaders// 如果这个readerCount>=0,说明没有写者if r >= rwmutexMaxReaders {race.Enable()fatal("sync: Unlock of unlocked RWMutex")}// 当前写锁期间累积了多少个阻塞的读者(readerCount),就释放几次readerSemfor i := 0; i < int(r); i++ {runtime_Semrelease(&rw.readerSem, false, 0)}// 释放写锁rw.w.Unlock()
}
  • 恢复readerCount为原本的值

  • 有多少个readerCount(写操作期间,后来的读操作被阻塞等待,读操作数量),就释放几次readerSem信号量,将所有等待的读操作全部唤醒

  • 释放写锁

读者释放锁RUnLock()
func (rw *RWMutex) RUnlock() {// 先将readerCount减1,释放读锁。如果readerCount<0,表示有写者在等,则进入rUnlockSlow()if 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")}// readerWait-1,将写者需要等待的读者-1// 如果readerWait-1后为0,表示这是最后一个读者,要发送信号量通知写者if rw.readerWait.Add(-1) == 0 {runtime_Semrelease(&rw.writerSem, false, 1)}
}
  • 先释放读锁(readerCount-1)

  • 如果有写者在等(readerCount<0),并且当此读者为写操作需要等待的最后一个读者时(readerWait-1==0),释放writerSem信号量通知写者进行写操作

写操作如何阻止读操作(正在写操作,则不能读)

每次读锁定将readerCount值+1,每次解除读锁定将该值-1

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

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

读操作如何阻止写操作(正在读操作,则不能写)

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

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

为什么写锁定不会被饿死(写优先)

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

  1. readerWait标记写操作到来时,前面正在执行的读操作的个数,等这些读操作完毕后readerWait减为0,通知写操作执行(由于只要有读操作到来,都会将readerCount+1,所以无法知道读操作的先后顺序,这里用readerWait临时记录写操作到来时前面的读操作个数,实现了给写操作“排队”的效果,使写操作不会被后续读操作“插队”)

写操作到来时,会把readerCount值拷贝到readerWait中,用于标记排在写操作前面的读者个数

写操作前面的读操作结束后,除了会递减readerCount,还会递减RWMutex.readerWait,当readerWait值变为0时(写操作前面的读操作都已结束),唤醒写操作

      2. 同时写操作一旦到来,就会将readerCount改为负值,表示有写操作在等待,使得写操作不会被后续到来的读操作抢占

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

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

相关文章

Linux(centos)安全狗

sdui进入操作页面 [rootlocalhost safedog_an_linux64_2.8.32947]# sdui维护 查看、启动或停止服务。 [rootiZbp1f0xuq9rc41s6gdvfyZ /]# systemctl status safedog [rootiZbp1f0xuq9rc41s6gdvfyZ /]# systemctl start safedog [rootiZbp1f0xuq9rc41s6gdvfyZ /]# systemct…

ES9 / ES2018 正则表达式增强

✅ 一、命名捕获组&#xff08;Named Capture Groups&#xff09;给捕获结果起名字&#xff0c;更易读、更易维护。&#x1f539; 传统写法&#xff08;位置识别&#xff09;&#xff1a;const result /(\d{4})-(\d{2})-(\d{2})/.exec("2025-07-31"); console.log(…

深入Java开发:Token的全方位解析与实战指南(下)

深入Java开发&#xff1a;Token的全方位解析与实战指南&#xff08;下&#xff09; 上一篇 深入Java开发&#xff1a;Token的全方位解析与实战指南&#xff08;上&#xff09; 五、Token 的生命周期与管理 5.1 Token 的生命周期状态 Token 的生命周期涵盖了从创建到最终失效…

第二十四天(数据结构:栈和队列)队列实践请看下一篇

栈和队列栈 &#xff1a; 是限定在表尾进行插入和删除操作的线性表实现是一回事&#xff0c;但是必须要满足栈的基本特点它的设计思路是:先进后出&#xff0c;后进先出栈有两端1 栈顶(top) &#xff1a;插入数据删除数据都只能在这一端访问也只能访问栈顶2 栈底(bottom) : 栈底…

三、Spark 运行环境部署:全面掌握四种核心模式

作者&#xff1a;IvanCodes 日期&#xff1a;2025年7月25日 专栏&#xff1a;Spark教程 Apache Spark 作为统一的大数据分析引擎&#xff0c;以其高性能和灵活性著称。要充分利用Spark的强大能力&#xff0c;首先需要根据不同的应用场景和资源环境&#xff0c;正确地部署其运行…

【Django】-2- 处理HTTP请求

一、request 请求 先理解&#xff1a;Request 是啥&#xff1f;用户访问你的网站时&#xff0c;会发一个 “请求包” &#x1f4e6; &#xff0c;里面装着&#xff1a;想访问啥路径&#xff1f;用啥方法&#xff08;GET/POST 等&#xff09;&#xff1f;带了啥头信息&#xff0…

飞算 JavaAI:突破效率边界的代码智能构造平台

飞算 JavaAI&#xff1a;突破效率边界的代码智能构造平台 一、引言&#xff1a;数字化浪潮下的开发效率困局与破局路径 当企业数字化转型驶入深水区&#xff0c;软件开发正面临需求迭代频次激增、人力成本高企、技术架构复杂化的多重挑战。传统开发模式中&#xff0c;从需求分…

国家科学技术奖答辩PPT案例_科技进步奖ppt制作_技术发明奖ppt设计美化_自然科学奖ppt模板 | WordinPPT

“国家科学技术奖”是在科学技术领域设立的最高荣誉&#xff0c;旨在奖励在科学技术进步活动中做出突出贡献的个人和组织&#xff0c;从而推动国家科学技术事业的发展&#xff0c;加快建设科技强国。科学技术奖是国内科技界的最高殿堂&#xff0c;是对做出杰出贡献的科技工作者…

如何通过黑白棋盘进行定位配准融合?(前后安装的两个相机)

一.总结: 完整流程 &#xff1a;硬件准备 → 数据采集 → 空间统一 → 相机标定&#xff08;内参畸变&#xff09; → 外参求解 → 定位配准融合 → 校验 → 生成映射表 → 上线remap验证 我们场景流程 &#xff1a;硬件准备 → 数据采集 → 空间统一 → 定位配准融合 → …

【node】token的生成与解析配置

在用户登录成功之后为了记录用户的登录状态通常会将用户信息编写为一个token&#xff0c;通过解析token判断用户是否登录。 token的生成 JSON Web Token&#xff08;JWT&#xff09; 是一种基于JSON的轻量级身份验证和授权机制。它是一种开放标准&#xff08;RFC 7519&#xff…

yolo 、Pytorch (5)IOU

一、简介 IOU的全称为交并比&#xff08;Intersection over Union&#xff09;&#xff0c;是目标检测中使用的一个概念&#xff0c;IoU计算的是“预测的边框”和“真实的边框”的交叠率&#xff0c;即它们的交集和并集的比值。最理想情况是完全重叠&#xff0c;即比值为1。 …

【银河麒麟服务器系统】自定义ISO镜像更新内核版本

自定义ISO镜像更新内核版本 镜像制作流程 环境 更新仓库 准备新版本内核包 内核清单简介 已下载软件包版本 更新内核包 更新镜像源 制作自动化镜像 修改引导 修改UEFI引导 传统引导 修改ks文件内容 打包镜像 mkisofs参数说明 封装镜像命令 常见问题解决方案 镜像制作流程 #merm…

JVM 调优中JVM的参数如何起到调优动作?具体案例,G1GC垃圾收集器参数调整建议

JVM调优参数 在JVM调优过程中&#xff0c;通过调整JVM参数可以优化Java应用程序的性能。不同的应用场景可能需要不同的调优策略和参数配置。下面将介绍几个常见的调优场景以及相应的JVM参数设置&#xff0c;并给出具体案例说明。 1. 堆内存大小调整 问题描述&#xff1a;应用程…

TGD第十一篇:卷积神经网络中的TGD特征

文章目录一、直觉上重要的视觉特征二、视觉神经网络首层试图自主学习 TGD 算子权重2.1 AlexNet2.2 Vision Transformer2.3 MLPMixer三、针对直觉的验证试验3.1 小样本集自然图像分类任务3.2 小样本集医学图像分割任务四、结语早在 2012 年&#xff0c;卷积神经网络 AlexNet 就已…

【源力觉醒 创作者计划】文心大模型开源:从封闭研发到生态共建的转折点

前言 人工智能的浪潮在近几年席卷全球&#xff0c;不仅颠覆了传统技术路径与行业习惯&#xff0c;更在大模型领域掀起了一场激烈的生态争夺战。自去年起&#xff0c;"百模大战"的硝烟弥漫&#xff0c;微软、谷歌、百度、阿里等科技巨头纷纷入局&#xff0c;在大模型的…

思科 UCS Fabric Interconnect 和 UCS Manager 简介

UCS Manager&#xff08;UCSM&#xff09;安装在 Fabric Interconnect&#xff08;FI&#xff09;上&#xff0c;并且是UCS架构的集中管理平台&#xff0c;允许你管理所有与计算、网络和存储相关的配置。1. UCS Manager 安装位置UCS Manager 是在 UCS Fabric Interconnect&…

C语言结构体、位段、枚举、联合体

结构体&#xff1a;定义&#xff1a;结构体就是一堆值的集合初始化&#xff1a;#include<stdio.h> #include <stddef.h> struct S {char ch;int n; };int main() {struct S s1 { a, 5 };S s2{ b,6 };printf("s1 ch:%c , n:%d\n", s1.ch, s1.n);printf(&…

AI产品经理面试宝典第61天:AI产品体验、数据安全与架构实战解析

1. 如何提升 AI 产品的用户体验? 1.1 问:如何提升 AI 产品的用户体验? 答: 提升 AI 产品的用户体验可以从以下几个方面入手: 可解释性增强:AI模型的输出往往较为“黑盒”,用户难以理解其决策逻辑。通过可视化、自然语言解释、关键特征展示等方式,增强用户对AI决策过程…

以微服务为基础搭建一套脚手架开始前的介绍

书接上回<java一个脚手架搭建-CSDN博客> 这个脚⼿架项⽬开发前&#xff0c;你要大概的了解一下这些东西&#xff1a; Java基础、IDEA使⽤、Maven基础 • Linux基础 • Springboot/Spring Cloud 基础 • MySQL基础 • Redis基础 • RabbitMQ基础 • Docker基础 • Git基…

Excel接入deepseek

先进入deepseek官网&#xff1a;DeepSeek | 深度求索 点击API开放平台&#xff1a; 确保余额里有钱: 创建APIkey: 复制到.txt文件中储存好 插入VBA代码&#xff1a; Function OptimizeEbayTitle(originalTitle As String) As StringDim Prompt As StringPrompt "作为…