go 源码解读 sync.RWMutex

news2025/1/23 15:04:07

sync.RWMutex

    • 简介
    • 源码
      • 结构
      • RLock
      • RUnlock
      • Unlock
      • go 运行时方法

简介

简述sync包中读写锁的源码。 (go -version 1.21)

读写锁(RWMutex)是一种并发控制机制,用于在多个 goroutine 之间对共享资源进行读写操作。它提供了两种锁定方式:读锁和写锁。
读锁(RLock):多个 goroutine 可以同时持有读锁,而不会阻塞彼此。只有当没有写锁被持有时,读锁才会被授予。这样可以实现多个 goroutine 并发地读取共享资源,提高程序性能。
写锁(Lock):写锁是排它的,当某个 goroutine 持有写锁时,其他所有 goroutine 都无法获得读锁或写锁。这是为了确保在写入共享资源时,没有其他 goroutine 在读或写该资源。

写锁是排他性的 :
(假设写锁不是排他性的, 新的读锁可以被获取) 反证:(多个写锁的情况下就不聊了)
现在有 一个goroutine1 获取读锁; 一个 goroutine2 获取写锁,但没有写锁被其他goroutine持有
然后有个goroutine3 想获取读锁,在假设的条件下 goroutine3 就会获取到读锁, 会导致goroutine2 无法获取写锁, 极端情况下会导致goroutine2 一直获取不到写锁 ---- 写锁饥饿
所以 为了读写公平, 还是把写锁优先级提高, 在写锁的情况下, 新的读锁无法被获取。

源码

结构

// RWMutex 结构体包含了用于读写互斥锁的各种状态和信号量。在 RWMutex 中,多个 goroutine 可以同时持有读锁,
// 但只有一个 goroutine 可以持有写锁。RWMutex 的实现确保了在写锁等待的情况下,新的读锁无法被获取,
// 从而防止了读锁长时间等待
type RWMutex struct {
    w           Mutex        // 用于写锁的互斥锁
    writerSem   uint32       // 写锁的信号量,用于等待读锁完成
    readerSem   uint32       // 读锁的信号量,用于等待写锁完成
    readerCount atomic.Int32 // 读者持有读者锁的数量
    readerWait  atomic.Int32 // 写者等待读锁的数量
}
// readerCount 
// 当读者加锁时, readerCount  +1 
// 当读者解锁时, readerCount  -1
// 当 readerCount 大于 0 时,表示有读者持有读锁。
// 如果 readerCount 等于 0,则表示当前没有读者持有读锁。
// 当 readerCount 等于 0 时,其他写者可以尝试获取写锁
// 当 readerCount < 0 , 说明有写者在等待, 读者需要等待写者释放写锁
// readerWait(写者等待读锁的数量):
// 当写者尝试获取写锁,但当前有读者持有读锁时,写者会被阻塞,并且 readerWait 会增加。
// 当读者释放读锁时,如果有写者在等待读锁,readerWait 会减少,并且可能唤醒等待的写者
// readerCount > 0:表示有读者持有读锁。
// readerCount == 0 且 readerWait > 0:表示有写者在等待读锁,阻塞中。
// readerCount == 0 且 readerWait == 0:表示当前没有读者持有读锁,且没有写者在等待读锁。此时其他写者可以尝试获取写锁。
// 这样的设计是为了在写者等待读锁时,不允许新的读者加入,以确保写者获得更公平的机会。

RLock

在这里插入图片描述

// Happens-before relationships are indicated to the race detector via:
// - Unlock  -> Lock:  readerSem
// - Unlock  -> RLock: readerSem
// - RUnlock -> Lock:  writerSem
//
// happends-before 用于描述时间发生的顺序,在 这里 (代码中的注释)
// Unlock 事件发生之前(happens-before)的 Lock 事件。
// Unlock 事件发生之前(happens-before)的 RLock 事件。
// RUnlock 事件发生之前(happens-before)的 Lock 事件


func (rw *RWMutex) RLock() {
	if race.Enabled {
		_ = rw.w.state
		race.Disable()
	}
	// 它将 readerCount 增加 1。如果结果小于 0,说明有一个写者拿着锁了, 这边需要等着
	if rw.readerCount.Add(1) < 0 {
		// A writer is pending, wait for it.
		// 在读写锁的情况下 执行锁的信号量 
		// 实际得等待操作, 调用底层代码, 等写者释放锁
		runtime_SemacquireRWMutexR(&rw.readerSem, false, 0)
	}
	if race.Enabled {
		race.Enable()
		race.Acquire(unsafe.Pointer(&rw.readerSem))
	}
}

race.Enabled : 竞态检测, 是go运行时提供的工具, 用于检测并发程序中的数据竞争问题,
使用 go run -race main.go 可以检测, 然后输出报告。
后面竞态检测代码 不说明

RUnlock

在这里插入图片描述

// RUnlock undoes a single RLock call;
// it does not affect other simultaneous readers.
// It is a run-time error if rw is not locked for reading
// on entry to RUnlock.
func (rw *RWMutex) RUnlock() {
	... // 竞态规则逻辑
	// readerCount 用于记录当前持有读锁的读者数量。如果读者计数减为负数,说明存在写者正在等待读锁释放
	if r := rw.readerCount.Add(-1); r < 0 {
		// Outlined slow-path to allow the fast-path to be inlined
		rw.rUnlockSlow(r)
	}
	... // 竞态规则逻辑
}
func (rw *RWMutex) rUnlockSlow(r int32) {
	... // 竞态规则逻辑
	// A writer is pending.
	// 减少读者等待计数。readerWait 记录正在等待读锁释放的读者数量。如果读者等待计数减为零,
	// 说明最后一个读者已经释放了读锁,可以唤醒等待的写者
	if rw.readerWait.Add(-1) == 0 {
		// The last reader unblocks the writer.
		// 释放写者的信号量
		runtime_Semrelease(&rw.writerSem, false, 1)
	}
}

在这里插入图片描述

func (rw *RWMutex) Lock() {
	... // 竞态规则逻辑
	// First, resolve competition with other writers.
	// 获取写锁,解决与其他写者的竞争
	rw.w.Lock()
	// Announce to readers there is a pending writer.
	// 将 readerCount 减去 rwmutexMaxReaders,然后再加上 rwmutexMaxReaders,目的是将 readerCount 设置为负数,
	// 表示有一个写者正在等待写锁。这会通过 readerWait 记录下来,并用于通知读者和写者。
	// 这边得  rw.readerCount 是一个负数, 在RLock 中有个判断 rw.readerCount <0 , 
	// 这一段就是实现了写者优先, 不管当前有没有读者拿着读锁, 接下来拿锁的读锁, 统统排我后面,不能影响我(写者), 等我(写者)处理完了再说
	r := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders
	// Wait for active readers.
	// 如果有活跃的读者,且将 readerWait 增加 r 后不为零,说明有读者正在等待读锁释放
	if r != 0 && rw.readerWait.Add(r) != 0 {
	// 获取写锁。
	// 它不是马上就能获取写锁,而是可能会被阻塞,需要等待写锁的释放
	// 当写者执行这个操作时,如果当前没有其他写者或读者持有锁,那么它会成功获取写锁,否则它会被阻塞,直到没有其他写者或读者持有锁
		runtime_SemacquireRWMutex(&rw.writerSem, false, 0)
	}
	... // 竞态规则逻辑
}

Unlock

在这里插入图片描述

// arrange for another goroutine to RUnlock (Unlock) it.
func (rw *RWMutex) Unlock() {
	... // 竞态规则逻辑

	// Announce to readers there is no active writer.
	// 将 readerCount 增加 rwmutexMaxReaders,用于通知活跃的读者,写者已经释放了写锁
	// 我( 写者)处理完了, 把 rw.readerCount 加回来了, 当前写锁写完了, 刚刚我(写者)拿锁以后 申请的读锁们, 可以唤醒了
	r := rw.readerCount.Add(rwmutexMaxReaders)
	... // 竞态规则逻辑
	// Unblock blocked readers, if any.
	// 遍历并逐个释放等待的读者,通过 runtime_Semrelease 信号量操作通知它们。
	for i := 0; i < int(r); i++ {
		runtime_Semrelease(&rw.readerSem, false, 0)
	}
	// Allow other writers to proceed.
	rw.w.Unlock()
	... // 竞态规则逻辑
}

go 运行时方法

1、runtime_SemacquireRWMutexR(&rw.readerSem, false, 0):

这个方法用于获取读锁。
&rw.readerSem 是一个信号量,表示等待读锁的读者队列。
false 表示不以 LIFO(LastIn, First Out)模式进行等待队列的管理。 、
0 表示无超时。

2、runtime_SemacquireRWMutex(&rw.writerSem, false, 0):

这个方法用于获取写锁。
&rw.writerSem 是一个信号量,表示等待写锁的写者队列。
false 表示不以 LIFO模式进行等待队列的管理。
0 表示无超时。

3、runtime_Semrelease(&rw.writerSem, false, 1):

这个方法用于释放写锁。
&rw.writerSem 是写锁等待队列的信号量。
false 表示不以 LIFO 模式进行等待队列的管理。
1 表示释放一个写者,通知等待的写者。

4、runtime_Semrelease(&rw.readerSem, false, 0):

这个方法用于释放读锁。
&rw.readerSem 是读锁等待队列的信号量。
false 表示不以 LIFO 模式进行等待队列的管理。
0表示释放一个读者,通知等待的读者

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

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

相关文章

10|记忆:通过Memory记住客户上次买花时的对话细节

10&#xff5c;记忆&#xff1a;通过Memory记住客户上次买花时的对话细节 在默认情况下&#xff0c;无论是 LLM 还是代理都是无状态的&#xff0c;每次模型的调用都是独立于其他交互的。也就是说&#xff0c;我们每次通过 API 开始和大语言模型展开一次新的对话&#xff0c;它…

Servlet中常用的三大API

HttpServlet 我们写 Servlet 代码的时候&#xff0c;首先第一步就是先创建类&#xff0c;继承自 HttpServlet&#xff0c;并重写其中的某些方法。我们实际开发的时候主要重写 doXXX 方法&#xff0c;很少会重写 init / destory / service。 因为这一些方法的调用时机&#xf…

【链表OJ—链表的回文结构】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 例如&#xff1a;…

Apache DolphinScheduler 3.1.9 版本发布:提升系统的稳定性和性能

&#x1f680;我们很高兴宣布&#xff0c;Apache DolphinScheduler 的最新版本 3.1.9 已正式发布&#xff01;此版本在 3.1.8 的基础上进行了关键的 bug 修复和文档更新&#xff0c;共计修复了 14 个 bug 和改进了 3 个文档。 主要更新亮点 本次更新重点解决了以下几个关键问题…

《PCI Express体系结构导读》随记 —— 第I篇 第1章 PCI总线的基本知识(19)

接前一篇文章&#xff1a;《PCI Express体系结构导读》随记 —— 第I篇 第1章 PCI总线的基本知识&#xff08;18&#xff09; 1.4 PCI总线的中断机制 1.4.3 中断请求的同步 在PCI总线中&#xff0c;INTx信号是一个异步信号。所谓异步是指INTx信号的传递并不与PCI总线的数据传送…

SpringBoot 医药咨询系统

概述 智慧医药系统&#xff08;smart-medicine&#xff09;是一个基于 SpringBoot 开发的Web 项目。整体页面简约大气&#xff0c;增加了AI医生问诊功能&#xff0c;功能设计的较为简单。 开源地址 https://gitcode.net/NVG_Haru/Java_04 界面预览 功能介绍 游客功能介绍 …

现实世界中的人工智能:工业制造的 4 个成功案例研究

现实世界中的人工智能&#xff1a;工业制造的 4 个成功案例研究 从抓鸡翅到建立整个虚拟工厂&#xff0c;各种规模的制造商都利用人工智能以更快的速度、更低的成本和更低的风险生产更多的产品。 我们能否让工厂变得足够聪明&#xff0c;在发生故障之前告诉我们&#xff1f;我…

C语言实现关键字匹配算法(复制即用)

文章目录 前言功能要求运行截图全部代码 前言 无套路&#xff0c;均已上机通过&#xff0c;求个关注求个赞&#xff0c;提供答疑解惑服务。 功能要求 一份C源代码存储在一个文本文件中&#xff0c;请统计该文件中关键字出现的频度&#xff0c;并按此频度对关键字进行排序。要…

Kafka安装及简单使用介绍

&#x1f353; 简介&#xff1a;java系列技术分享(&#x1f449;持续更新中…&#x1f525;) &#x1f353; 初衷:一起学习、一起进步、坚持不懈 &#x1f353; 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正&#x1f64f; &#x1f353; 希望这篇文章对你有所帮助,欢…

政务大数据能力平台建设方案:文件全文30页,附下载

关键词&#xff1a;智慧政务解决方案&#xff0c;智慧政务建设&#xff0c;智慧政务服务平台&#xff0c;智慧政务大数据&#xff0c;数字政务一体化平台。大数据&#xff0c;政务大数据建设 一、智慧政务建设需求 1、政务服务需求&#xff1a;智慧政务建设需要满足人民群众的…

Jenkins 系列:Jenkins 安装(Windows、Mac、Centos)和简介

文章目录 简介发展历史应用场景 Jenkins 安装部署先决条件硬件要求软件包下载war 包部署linux 系统部署mac 系统部署windows 系统部署安装后基本配置解锁自定义 jenkins 插件创建用户配置更新站点 配置文件 简介 Jenkins前身是 Hudson&#xff0c;使用 java 语言开发的自动化发…

基于SpringBoot实现的前后端分离电影评分项目,功能:注册登录、浏览影片、热门影片、搜索、评分、片单、聊天、动态

一、项目介绍 本项目主要基于SpringBoot、Mybatis-plus、MySQL、Redis实现的影片评分项目。 本系统是前后端分离的&#xff0c;分别由三个子项目构成&#xff1a;java服务端、用户前端、管理员管理前端 关键词&#xff1a;springboot java vue mysql reids websocket 毕业设计…

【Leetcode】1154. 一年中的第几天

文章目录 题目思路代码 题目 1154. 一年中的第几天链接 思路 题目要求是给定一个字符串 date&#xff0c;它代表一个日期&#xff0c;采用标准的 YYYY-MM-DD 格式。需要计算这个日期是当年的第几天。 首先&#xff0c;我们可以通过字符串的索引来提取年、月和日的数值&…

python的pywebio库给孩子做加减法数学题

效果展示 程序执行后&#xff0c;打开浏览器&#xff0c;展示一些100以内的加减法混合运算的数学题并输入答案后判断对错&#xff0c;这样倒是省了买教材的钱了。 在题目下方的框中&#xff0c;输入答案&#xff0c;然后点击提交后&#xff0c; 会输出结果 pywebio库介绍 安装…

Resnet BatchNormalization 迁移学习

时间&#xff1a;2015 网络中的亮点&#xff1a; 超深的网络结构&#xff08;突破1000层&#xff09;提出residual模块使用Batch Normalization加速训练&#xff08;丢弃dropout&#xff09; 层数越深效果越好&#xff1f; 是什么样的原因导致更深的网络导致的训练效果更差呢…

计算机组成原理复习7

内存管理 文章目录 内存管理存储器概述存储器的分类按在计算机中的作用&#xff08;层次&#xff09;分类按存储介质分类按存取方式分类按信息的可保存性分类 存储器的性能指标存储容量单位成本存储速度&#xff1a;数据传输率数据的宽度/存储周期 存储器的层次化结构多级存储系…

【实用工具】vim常用命令

快速移动(上下左右箭头可替代) 左移 h 右移 l 下移 j 上移 K在本行操作 0 移动到本行行首 ^ 移动到本行的第一个不是 blank 字符 $ 移动到本行行尾 w 光标移动到下一个单词的开头 e 光标移动到下一个单词的结尾跨行移动光标 nG 光标定位到第n行的行首 gg 光标定位到第一行的…

SPI机制原理+使用

一、概述 SPI全称&#xff08;Service Provider Interface&#xff09;&#xff0c;是JDK内置的一种服务提供发现机制&#xff1b;SPI机制提供了组件发现和注册方式&#xff0c;可以为应用程序提供灵活的插件机制&#xff0c; 主要原理&#xff1a;接口 反射 配置文件。 二、…

Visual Studio 2015 中 SDL2 开发环境的搭建

Visual Studio 2015 中 SDL2 开发环境的搭建 Visual Studio 2015 中 SDL2 开发环境的搭建新建控制台工程拷贝并配置 SDL2 开发文件拷贝 SDL2 开发文件配置 SDL2 开发文件 测试SDL2 开发文件的下载链接 Visual Studio 2015 中 SDL2 开发环境的搭建 新建控制台工程 新建 Win32 …

学习使用echats实现双刻度echarts双Y轴,左右Y轴数据的方法

学习使用echats实现双刻度echarts双Y轴&#xff0c;左右Y轴数据的方法 代码效果图 代码 <!--此示例下载自 https://echarts.apache.org/examples/zh/editor.html?cline-stack&langjs --> <!DOCTYPE html> <html lang"en" style"height: 10…