Go源码--sync库(2)

简介

这边文章主要讲解 Sync.Cond和Sync.Rwmutex

Sync.Cond

简介 sync.Cond 经常用来处理 多个协程等待 一个协程通知 这种场景, 主要 是阻塞在某一协程中 等待被另一个协程唤醒 继续执行 这个协程后续的功能。cond经常被用来协调协程对某一资源的访问 ants协程池就用了这种机制
本文主要讲解Cond 的源码 ,关于其使用已经有许多例子了 本文直接略过
cond 的结构体如下

type Cond struct {noCopy noCopy // 告诉编译器 本结构体不能复制// L is held while observing or changing the conditionL Locker            notify  notifyList  // 在 Wait 上阻塞的线程 这边只记录 阻塞携程的 指针 真正阻塞是调用的GMP模型 相关功能checker copyChecker // 复制检查
}
//使用时 需要 新建一个Cond变量 入参必须有一个锁 用来 锁定 Cond 的 Wait()调用所涉及的资源
func NewCond(l Locker) *Cond {return &Cond{L: l}
}

其 主要有三个函数 如下

Wait()

用来阻塞 协程
其源码如下

func (c *Cond) Wait() {c.checker.check()                     // 复制检查t := runtime_notifyListAdd(&c.notify) // 等待数量+1; 其使用了 汇编的 LOCK 命令来实现加1的原子性操作c.L.Unlock()                          // 解锁 ;Wait()代码调用之前必须有 Lock ,可以看到 锁c.L 的作用域直接到了这里// 因为 Wait() 一般用在 协程中 且共享内存(也就是变量等)runtime_notifyListWait(&c.notify, t)  // 所有协程都阻塞在这里;  大概功能是 首先将本协程指针放入notifyList等待队列// 然后进行GMP模型调度 释放当前M 将当前 G指针加入等待队列等待被唤醒 M开始等待可用的G// 所以 g指针存在于两个地方 一个 notifyList 列表 一个 等待队列 这样方便唤醒c.L.Lock()                            // 解锁
}

实现wait 效果的 是 runtime_notifyListWait(…)函数 我们来研究下 其代码位置是 runtime/runtime2.go/notifyListWait
源码如下

func notifyListWait(l *notifyList, t uint32) {// 上锁 细节可以自行研究lockWithRank(&l.lock, lockRankNotifyList)// Return right away if this ticket has already been notified.if less(t, l.notify) {unlock(&l.lock)return}// Enqueue itself.// sudog 是某个协程的 阻塞状态信息s := acquireSudog()// 获取当前协程的指针s.g = getg()// 可以看做 当前协程在 等待列表中的索引s.ticket = ts.releasetime = 0t0 := int64(0)if blockprofilerate > 0 {t0 = cputicks()s.releasetime = -1}// 将协程信息 插入 尾部if l.tail == nil {l.head = s} else {l.tail.next = s}l.tail = s// 这里涉及到 GMP模型调度 大概功能是 释放当前M 将当前 G加入等待队列等待被唤醒 M开始等待可用的Ggoparkunlock(&l.lock, waitReasonSyncCondWait, traceBlockCondWait, 3)if t0 != 0 {blockevent(s.releasetime-t0, 2)}// 释放当前 sudog结构体releaseSudog(s)
}
Signal()

唤醒 某一个 调用 Wait()阻塞的协程 继续执行其后续代码
其源码如下

func (c *Cond) Signal() {c.checker.check()runtime_notifyListNotifyOne(&c.notify)  // link runtime/sema.go/notifyListNotifyOne// notifyListNotifyOne 主要使用了GMP调度器的唤醒功能(runtime/proc.go/goready(...)):从notifyList获取某个需要唤醒的g协程指针,// 使用GMP调度算法 唤醒这个g协程// 将它(g)放入(使用M 操作)运行队列中 必要时唤醒处理器(p)处理这个协程
}

其中runtime_notifyListNotifyOne的唤醒功能使用的是 GMP调度算法 的 ready 调用链 如下
在这里插入图片描述

ready源码如下

func ready(gp *g, traceskip int, next bool) {if traceEnabled() {traceGoUnpark(gp, traceskip)}// 获取 当前协程状态status := readgstatus(gp)// Mark runnable.// 获取当前协程 所属的 Mmp := acquirem() // disable preemption because it can be holding p in a local varif status&^_Gscan != _Gwaiting {dumpgstatus(gp)throw("bad g->status in ready")}// status is Gwaiting or Gscanwaiting, make Grunnable and put on runq// 将当前协程状态 由等待中 变为可运行casgstatus(gp, _Gwaiting, _Grunnable)// 通过 M 将协程 G 放入可运行队列runqput(mp.p.ptr(), gp, next)// 唤醒一个 Pwakep()releasem(mp)
}

可以看到 这段代码 有了我们熟悉的G、M 和P

Broadcast()

唤醒所有 调用 Wait()阻塞的协程 继续执行其后续代码
其源码如下

func (c *Cond) Broadcast() {c.checker.check()runtime_notifyListNotifyAll(&c.notify) // 主要功能跟 runtime_notifyListNotifyOne 一样 但这个函数 调用的for循环 遍历 整个notifyList列表 唤醒所有协程
}

其底层也就相当 for 循环 执行 了 ready函数

Sync.Rwmutex

读写锁 是为了 解决 mutex的严格互斥的缺点,读读可以并发、读写有条件互斥、写写严格互斥
有争议的就是 读写有条件互斥 这是什么意思呢 我们首先 介绍下 读和写的两种优先模式

  1. 读写时 读优先
    只要有读操作来写操作就阻塞,一直到没有读操作为止,可能会造成写锁饥饿, 这种适合读操作远远大于写操作的场景
  2. 读写时 写优先
    写操作来时 后续的读操作全部阻塞 但是 写操作前的读操作需要完成
    rwmutex 就是采用写优先 所以 有条件互斥 是 写之前的读操作 可以继续完成 写之后的读操作就需要阻塞了

接下来我们来看源码
RWMutex 结构体如下:

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 atomic.Int32 // number of pending readers  // 当前挂起的读操作数,也就是写操作之前和之后发生的 在执行中的总的读操作。readerCount 并发安全 使用 Lock 锁总线readerWait  atomic.Int32 // number of departing readers  // 比写操作早到的读操作的数量 执行了 RLock() 但是没执行 RUnlock  // 执行链路  Rlock--->Lock()-->Runlock()
}

其重要的 函数有四个 RLock()、Runlock()、Lock()、Unlock(),其中前两个是控制读操作的,后两个控制写。

  • RLock():根据一定条件来使得读协程阻塞
  • Runlock():根据一定条件来唤醒写协程
  • Lock(): 根据一定条件来阻塞写协程
  • Unlock():根据一定条件来唤醒其后到达的读协程 并释放写锁

我们分别来看下其代码:

RLock()
func (rw *RWMutex) RLock() {if race.Enabled {_ = rw.w.staterace.Disable()}if rw.readerCount.Add(1) < 0 { // 小于0 证明前方有写操作(因为写操作会首先readerCount置为小于0) 则后来的读操作阻塞// A writer is pending, wait for it.runtime_SemacquireRWMutexR(&rw.readerSem, false, 0) // 写之后的读操作阻塞,采用GMP模型 中的功能 将当前G 睡眠 释放 M}if race.Enabled {race.Enable()race.Acquire(unsafe.Pointer(&rw.readerSem))}
}
Runlock()
func (rw *RWMutex) RUnlock() { // 执行这个函数 有两种情况if race.Enabled {_ = rw.w.staterace.ReleaseMerge(unsafe.Pointer(&rw.writerSem))race.Disable()}if r := rw.readerCount.Add(-1); r < 0 { // 挂起的数量-1 如果挂起的协程数量是负数 说明 有写操作阻塞 则需要检查是否唤起写操作// 如果r>=0 :  可能是 Rlock-->Runlock() --->Lock()-->Unlock() 这种情况 readerCount不为负值// Outlined slow-path to allow the fast-path to be inlined// r<0 开始递减 在写操作之前挂起的读操作数 也就是 readerWait 数 什么情况下会造成这种情况呢 在写操作之前执行 这种情况是 执行完毕 Rlock() 后 写操作 Lock() Runlock()// 也就是 Rlock--->Lock()-->Runlock() 这种顺序  这种情况会造成 readerCount为负值rw.rUnlockSlow(r)}if race.Enabled {race.Enable()}
}

其中 rw.rUnlockSlow( r)的作用是 readerWait数量-1 达到条件后 唤醒写操作 代码如下

func (rw *RWMutex) rUnlockSlow(r int32) {if r+1 == 0 || r+1 == -rwmutexMaxReaders {race.Enable()fatal("sync: RUnlock of unlocked RWMutex")}// A writer is pending.if rw.readerWait.Add(-1) == 0 { // 写之前的读协程数量-1// The last reader unblocks the writer. // 写之前的最后一个 读操作 解锁 写操作runtime_Semrelease(&rw.writerSem, false, 1)}
}
Lock()
func (rw *RWMutex) Lock() {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 := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders // 将 readerCount 置为负数 用于阻塞后续 读操作;r是readerCount原始值// Wait for active readers.if r != 0 && rw.readerWait.Add(r) != 0 { // 将现有 写协程之前的读数量 加入到 等待数量中去 翻译成人话 比写操作早到的 读操作数量 如果不是0 也就是大于0 则写操作需要挂起runtime_SemacquireRWMutex(&rw.writerSem, false, 0) // 写协程挂起;GMP 模型 G进入等待队列休眠 M释放  注意:这里是获得写锁的协程睡眠}if race.Enabled {race.Enable()race.Acquire(unsafe.Pointer(&rw.readerSem))race.Acquire(unsafe.Pointer(&rw.writerSem))}
}

其中 const rwmutexMaxReaders = 1 << 30 为什么是这个数 不是 1<<31-1
解答:readerCount 高31位表示是否有写锁,高32表示是否有写锁在等待 则最大就是 1<<30-1 为了使得 其操作后为确定为负数 所以取值 1<<30

Unlock()
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 := rw.readerCount.Add(rwmutexMaxReaders) // 将readerCount 还原为原始值 说明没有 写操作了,这时 其值是 写操作后续的读操作if r >= rwmutexMaxReaders {race.Enable()fatal("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()}
}

无论是mutex还是rwmutex 其代码量都不大 但是逻辑都比较复杂 需要反复研读大神的代码

下面我们来看下 各种情况的 readCount 和 readWait 的数量 如下图
在这里插入图片描述

大家思考下 引入 rwmutex时能不能仅仅使用RLock()和Runlock()或者Lock()和Unlock()

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

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

相关文章

Win10 Edge提示兼容性问题打不开|解决浏览器兼容性问题

Edge有时候会与某些安全软件不兼容&#xff0c;导致报错 报错代码&#xff1a;STATUS_INVALID_IMAGE_HASH 解决Edge浏览器兼容性问题方法/步骤&#xff1a; 1、按 Win R 组合键&#xff0c;打开运行&#xff0c;并输入 regedit 命令&#xff0c;确定或回车&#xff0c;可以…

SAP ERP系统主要模块简介

SAP系统通过提供一系列高度灵活的模块&#xff0c;满足企业在不同业务领域的需求。这些模块不仅功能齐全且相对独立&#xff0c;但它们之间又能紧密协作&#xff0c;共同构筑一个协同高效的工作环境。 财务会计&#xff08;FI&#xff09;模块 它涵盖了总账、应收账款、应付账…

DexCap——斯坦福李飞飞团队泡茶机器人:更好数据收集系统的原理解析、源码剖析

前言 2023年7月&#xff0c;我司组建大模型项目开发团队&#xff0c;从最开始的论文审稿&#xff0c;演变成目前的两大赋能方向 大模型应用方面&#xff0c;以微调和RAG为代表 除了论文审稿微调之外&#xff0c;目前我司内部正在逐一开发论文翻译、论文对话、论文idea提炼、论…

k8s:优雅关闭pod的简单例子

先通过Dockerfile创建一个image vim Dockerfie <<<< 内容如下&#xff1a; FROM centosRUN sed -i -e "s|mirrorlist|#mirrorlist|g" /etc/yum.repos.d/CentOS-* RUN sed -i -e "s|#baseurlhttp://mirror.centos.org|baseurlhttp://vault.centos.o…

Qsemaphore

Qsemaphore 实现 给while循环阻塞延时 基本思路就是&#xff1a; whlie循环里面 通过m&#xff3f;bthreadFlag&m_bStatus这两个标志位&#xff0c;判断是否进入while循环&#xff0c;再根据40行的acquire&#xff08;&#xff09;来阻塞循环&#xff0c;因为定时器的槽函数…

SQL Server数据库xp_cmdshell提权笔记

文章目录 一、简介二、搭建环境三、利用条件1、查询 xp_cmdshell 是否开启&#xff0c;返回为1则证明存在2、判断权限是不是sa&#xff0c;回是1说明是sa3、开启xp_cmdshell4、关闭xp_cmdshell 四、获取数据库权限1、成功获取sqlserver&#xff0c;进行登陆2、开启xp_cmdshell权…

代码随想录算法训练营第31天(py)| 贪心 | 455.分发饼干、376. 摆动序列、53. 最大子序和

455.分发饼干 力扣链接 假设你是一位很棒的家长&#xff0c;想要给你的孩子们一些小饼干。但是&#xff0c;每个孩子最多只能给一块饼干。 对每个孩子 i&#xff0c;都有一个胃口值 g[i]&#xff0c;这是能让孩子们满足胃口的饼干的最小尺寸&#xff1b;并且每块饼干 j&#…

Docker|了解容器镜像层(1)

引言 容器非常神奇。它们允许简单的进程表现得像虚拟机。在这种优雅的底层是一组模式和实践&#xff0c;最终使一切运作起来。在设计的根本是层。层是存储和分发容器化文件系统内容的基本方式。这种设计既出人意料地简单&#xff0c;同时又非常强大。在今天的帖子[1]中&#xf…

29网课交单平台 epay.php SQL注入漏洞复现

0x01 产品简介 29网课交单平台是一个专注于在线教育和知识付费领域的交单平台。该平台基于PHP开发,通过全开源修复和优化,为用户提供了高效、稳定、安全的在线学习和交易环境。作为知识付费系统的重要组成部分,充分利用了互联网的优势,为用户提供了便捷的支付方式、高效的…

继承-进阶

父子类成员共享 普通成员对象/父子间不共享&#xff0c; 成员独立 函数成员共享&#xff08;函数不存储在对象中&#xff09; 子类由两部分构成&#xff1a;父类中继承的成员和子类中新定义成员 继承方式 子类中存在父类private成员但不可直接访问&#xff08;及时在类中&am…

微信如何防止被对方拉黑删除?一招教你解决!文末附软件!

你一定不知道&#xff0c;微信可以防止被对方拉黑删除&#xff0c;秒变无敌。只需一招就能解决&#xff01;赶快来学&#xff01;文末有惊喜&#xff01; 惹到某些重要人物&#xff08;比如女朋友&#xff09;&#xff0c;被删除拉黑一条龙&#xff0c;那真的是太令人沮丧了&a…

加密经济浪潮:探索Web3对金融体系的颠覆

随着区块链技术的快速发展&#xff0c;加密经济正在成为全球金融领域的一股新的浪潮。而Web3作为下一代互联网的代表&#xff0c;以其去中心化、可编程的特性&#xff0c;正深刻影响着传统金融体系的格局和运作方式。本文将深入探讨加密经济对金融体系的颠覆&#xff0c;探索We…

机器学习实验----支持向量机(SVM)实现二分类

目录 一、介绍 (1)解释算法 (2)数据集解释 二、算法实现和代码介绍 1.超平面 2.分类判别模型 3.点到超平面的距离 4.margin 间隔 5.拉格朗日乘数法KKT不等式 (1)介绍 (2)对偶问题 (3)惩罚参数 (4)求解 6.核函数解决非线性问题 7.SMO (1)更新w (2)更新b 三、代…

此表单不安全,因此系统已关闭自动填充功能

问题截图&#xff1a; 截图就不放了&#xff0c;公司的系统不方便&#xff0c;就是form表单会有个提示“此表单不安全&#xff0c;因此系统已关闭自动填充功能” 解决思路&#xff1a; 1、问题原因 使用https访问&#xff0c;但表单提交地址是http的 2、查看表单配置 表单…

MSP430单片机控制流水灯,Proteus仿真

作品功能 本项目利用MSP430单片机控制一个简单的流水灯&#xff0c;通过按键切换流水灯的模式。用户可以通过按键控制LED灯的方向&#xff0c;从左向右或从右向左依次点亮。 作品的硬件材料 MSP430单片机 具体型号&#xff1a;MSP430G2553 LED灯 数量&#xff1a;8个类型&…

文本审核纠错

探索高效文本审查利器&#xff1a;Word Checker-CSDN博客 GitHub - shibing624/pycorrector: pycorrector is a toolkit for text error correction. 文本纠错&#xff0c;实现了Kenlm&#xff0c;T5&#xff0c;MacBERT&#xff0c;ChatGLM3&#xff0c;LLaMA等模型应用在纠错…

如何设置vue3项目中默认的背景为白色

方法1&#xff1a;通过CSS全局样式 在全局CSS文件中设置&#xff1a; 如果你的项目中有全局的CSS文件&#xff08;如App.vue或专门的CSS文件&#xff09;&#xff0c;你可以直接设置body或html标签的背景颜色。 在src/assets文件夹中&#xff08;或者任何你存放CSS文件的地方&a…

js解析成语法树以及还原

const {parse} require("babel/parser"); const traverse require("babel/traverse").default; const generator require("babel/generator").default;// 1.定义要处理的代码 const jscode function square(n) {return n * n; };// 2.使用ba…

【人工智能】流行且重要的智能算法整理

✍&#x1f3fb;记录学习过程中的输出&#xff0c;坚持每天学习一点点~ ❤️希望能给大家提供帮助~欢迎点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;指点&#x1f64f; 小记&#xff1a; 今天在看之前写的文档时&#xff0c;发现有人工智能十大算法的内容&#xf…

国标GB/T 28181详解:国标GBT28181-2022的客户端主动发起历史视音频回放流程

目录 一、定义 二、作用 1、提供有效的数据回顾机制 2、增强监控系统的功能性 3、保障数据传输与存储的可靠性 4、实现精细化的操作与控制 5、促进监控系统的集成与发展 三、历史视音频回放的基本要求 四、命令流程 1、流程图 2、流程描述 五、协议接口 1、会话控…