go 源码解读 sync.RWMutex

2023-12-29 07:36
文章标签 源码 go 解读 sync rwmutex

本文主要是介绍go 源码解读 sync.RWMutex,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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.staterace.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 inlinedrw.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表示释放一个读者,通知等待的读者

这篇关于go 源码解读 sync.RWMutex的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/548799

相关文章

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

从基础到高级详解Go语言中错误处理的实践指南

《从基础到高级详解Go语言中错误处理的实践指南》Go语言采用了一种独特而明确的错误处理哲学,与其他主流编程语言形成鲜明对比,本文将为大家详细介绍Go语言中错误处理详细方法,希望对大家有所帮助... 目录1 Go 错误处理哲学与核心机制1.1 错误接口设计1.2 错误与异常的区别2 错误创建与检查2.1 基础

MySQL之搜索引擎使用解读

《MySQL之搜索引擎使用解读》MySQL存储引擎是数据存储和管理的核心组件,不同引擎(如InnoDB、MyISAM)采用不同机制,InnoDB支持事务与行锁,适合高并发场景;MyISAM不支持事务,... 目录mysql的存储引擎是什么MySQL存储引擎的功能MySQL的存储引擎的分类查看存储引擎1.命令

Go语言中json操作的实现

《Go语言中json操作的实现》本文主要介绍了Go语言中的json操作的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录 一、jsOChina编程N 与 Go 类型对应关系️ 二、基本操作:编码与解码 三、结构体标签(Struc

Spring的基础事务注解@Transactional作用解读

《Spring的基础事务注解@Transactional作用解读》文章介绍了Spring框架中的事务管理,核心注解@Transactional用于声明事务,支持传播机制、隔离级别等配置,结合@Tran... 目录一、事务管理基础1.1 Spring事务的核心注解1.2 注解属性详解1.3 实现原理二、事务事

Linux五种IO模型的使用解读

《Linux五种IO模型的使用解读》文章系统解析了Linux的五种IO模型(阻塞、非阻塞、IO复用、信号驱动、异步),重点区分同步与异步IO的本质差异,强调同步由用户发起,异步由内核触发,通过对比各模... 目录1.IO模型简介2.五种IO模型2.1 IO模型分析方法2.2 阻塞IO2.3 非阻塞IO2.4

使用Go调用第三方API的方法详解

《使用Go调用第三方API的方法详解》在现代应用开发中,调用第三方API是非常常见的场景,比如获取天气预报、翻译文本、发送短信等,Go作为一门高效并发的编程语言,拥有强大的标准库和丰富的第三方库,可以... 目录引言一、准备工作二、案例1:调用天气查询 API1. 注册并获取 API Key2. 代码实现3

基于Go语言开发一个 IP 归属地查询接口工具

《基于Go语言开发一个IP归属地查询接口工具》在日常开发中,IP地址归属地查询是一个常见需求,本文将带大家使用Go语言快速开发一个IP归属地查询接口服务,有需要的小伙伴可以了解下... 目录功能目标技术栈项目结构核心代码(main.go)使用方法扩展功能总结在日常开发中,IP 地址归属地查询是一个常见需求:

MySQL8.0临时表空间的使用及解读

《MySQL8.0临时表空间的使用及解读》MySQL8.0+引入会话级(temp_N.ibt)和全局(ibtmp1)InnoDB临时表空间,用于存储临时数据及事务日志,自动创建与回收,重启释放,管理高... 目录一、核心概念:为什么需要“临时表空间”?二、InnoDB 临时表空间的两种类型1. 会话级临时表

java 恺撒加密/解密实现原理(附带源码)

《java恺撒加密/解密实现原理(附带源码)》本文介绍Java实现恺撒加密与解密,通过固定位移量对字母进行循环替换,保留大小写及非字母字符,由于其实现简单、易于理解,恺撒加密常被用作学习加密算法的入... 目录Java 恺撒加密/解密实现1. 项目背景与介绍2. 相关知识2.1 恺撒加密算法原理2.2 Ja