【Golang 面试 - 进阶题】每日 3 题(二)
✍个人博客:Pandaconda-CSDN博客
📣专栏地址:http://t.csdnimg.cn/UWz06
📚专栏简介:在这个专栏中,我将会分享 Golang 面试中常见的面试题给大家~
❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力
4. R WMutex 实现
sync.RWMutex
是 Go 语言标准库提供的一种读写锁,用于在多个 goroutine 同时访问共享资源时进行保护。与 sync.Mutex
类似,sync.RWMutex
也是通过互斥锁实现的,但是它允许多个 goroutine 同时获取读锁,而只允许一个 goroutine 获取写锁。
下面是一个简单的 sync.RWMutex
的实现示例:
type RWMutex struct {writerSem chan struct{} // 写者用的信号量readerSem chan struct{} // 读者用的信号量readerCount int // 当前持有读锁的goroutine数量writerCount int // 当前持有写锁的goroutine数量readerWait int // 正在等待读锁的goroutine数量writerWait int // 正在等待写锁的goroutine数量writerLocked bool // 是否有goroutine持有写锁
}
func NewRWMutex() *RWMutex {return &RWMutex{writerSem: make(chan struct{}, 1),readerSem: make(chan struct{}, 1),}
}
// 获取读锁
func (m *RWMutex) RLock() {// 获取读锁的过程需要加锁m.writerSem <- struct{}{} // 防止写者获取锁m.readerSem <- struct{}{} // 获取读锁的信号量// 更新状态m.readerCount++if m.writerLocked || m.writerWait > 0 {m.readerWait++<-m.readerSem // 等待写者释放锁m.readerWait--}// 释放加锁时获取的信号量<-m.writerSem
}
// 释放读锁
func (m *RWMutex) RUnlock() {// 获取读锁的过程需要加锁m.writerSem <- struct{}{} // 防止写者获取锁// 更新状态m.readerCount--if m.readerCount == 0 && m.writerWait > 0 {<-m.writerSem // 优先唤醒写者}// 释放加锁时获取的信号量<-m.writerSem
}
// 获取写锁
func (m *RWMutex) Lock() {// 获取写锁的过程需要加锁m.writerSem <- struct{}{} // 防止其他写者获取锁m.writerWait++// 等待其他goroutine释放读锁或写锁for m.writerLocked || m.readerCount > 0 {<-m.readerSem}// 更新状态m.writerWait--m.writerLocked = true// 释放加锁时获取的信号量<-m.writerSem
}
// 释放写锁
func (m *RWMutex) Unlock() {// 获取写锁的过程需要加锁m.writerSem <- struct{}{}// 更新状态m.writerLocked = falseif m.writerWait > 0 {<-m.writerSem} else if m.readerWait > 0 {for i := 0; i < m.readerCount; i++ {m.readerSem <- struct{}{} // 优先唤醒读者}}// 释放加锁时获取的信号量<-m.writerSem
}
在这个实现中,sync.RWMutex
包含以下成员:
-
writerSem
和readerSem
:两个用于同步的信号量通道。写锁会在writerSem
上等待,读锁会在readerSem
上等待。 -
readerCount
和writerCount
:当前持有读锁和写锁的 goroutine 数量。 -
readerWait
和writerWait
:正在等待读锁和写锁的 goroutine 数量。 -
writerLocked
:标记当前是否有 goroutine 持有写锁。
在读锁和写锁获取和释放的过程中,都需要先获取 writerSem
信号量防止其他写者获取锁。获取读锁时还需要获取 readerSem
信号量,而获取写锁时需要等待其他 goroutine 释放读锁或写锁。
这个实现中有两个重要的细节:
-
优先唤醒写者:在释放读锁或写锁时,如果有正在等待的写锁 goroutine,应该优先唤醒它们,因为写锁的优先级更高。
-
读锁的等待问题:在等待读锁的 goroutine 中,如果有其他 goroutine 正在持有写锁或等待写锁,那么这些读锁 goroutine 应该等待写锁 goroutine 释放锁,避免因等待读锁而导致写锁饥饿。
5. R WMutex 注意事项
-
RWMutex 是单写多读锁,该锁可以加多个读锁或者一个写锁。
-
读锁占用的情况下会阻止写,不会阻止读,多个 Goroutine 可以同时获取读锁。
-
写锁会阻止其他 Goroutine(无论读和写)进来,整个锁由该 Goroutine 独占。
-
适用于读多写少的场景。
-
RWMutex 类型变量的零值是一个未锁定状态的互斥锁。
-
RWMutex 在首次被使用之后就不能再被拷贝。
-
RWMutex 的读锁或写锁在未锁定状态,解锁操作都会引发 panic。
-
RWMutex 的一个写锁去锁定临界区的共享资源,如果临界区的共享资源已被(读锁或写锁)锁定,这个写锁操作的 goroutine 将被阻塞直到解锁。
-
RWMutex 的读锁不要用于递归调用,比较容易产生死锁。
-
RWMutex 的锁定状态与特定的 goroutine 没有关联。一个 goroutine 可以 RLock(Lock),另一个 goroutine 可以 RUnlock(Unlock)。
-
写锁被解锁后,所有因操作锁定读锁而被阻塞的 goroutine 会被唤醒,并都可以成功锁定读锁。
-
读锁被解锁后,在没有被其他读锁锁定的前提下,所有因操作锁定写锁而被阻塞的 Goroutine,其中等待时间最长的一个 Goroutine 会被唤醒。
6. Go 读写锁的实现原理?
概念:
读写互斥锁 RWMutex,是对 Mutex 的一个扩展,当一个 goroutine 获得了读锁后,其他 goroutine 可以获取读锁,但不能获取写锁;当一个 goroutine 获得了写锁后,其他 goroutine 既不能获取读锁也不能获取写锁(只能存在一个写者或多个读者,可以同时读)。
使用场景:
读多于写的情况(既保证线程安全,又保证性能不太差)。
底层实现结构:
互斥锁对应的是底层结构是 sync.RWMutex 结构体,,位于 src/sync/rwmutex.go 中
type RWMutex struct {w Mutex // 复用互斥锁writerSem uint32 // 信号量,用于写等待读readerSem uint32 // 信号量,用于读等待写readerCount int32 // 当前执行读的 goroutine 数量readerWait int32 // 被阻塞的准备读的 goroutine 的数量
}
操作:
读锁的加锁与释放
func (rw *RWMutex) RLock() // 加读锁
func (rw *RWMutex) RUnlock() // 释放读锁
加读锁
func (rw *RWMutex) RLock() {
// 为什么readerCount会小于0呢?往下看发现writer的Lock()会对readerCount做减法操作(原子操作)if atomic.AddInt32(&rw.readerCount, 1) < 0 {// A writer is pending, wait for it.runtime_Semacquire(&rw.readerSem)}
}
atomic.AddInt32(&rw.readerCount, 1)
调用这个原子方法,对当前在读的数量加 1,如果返回负数,那么说明当前有其他写锁,这时候就调用 runtime_SemacquireMutex
休眠当前 goroutine 等待被唤醒。
释放读锁
解锁的时候对正在读的操作减 1,如果返回值小于 0 那么说明当前有在写的操作,这个时候调用 rUnlockSlow
进入慢速通道。
func (rw *RWMutex) RUnlock() {if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {rw.rUnlockSlow(r)}
}
被阻塞的准备读的 goroutine 的数量减 1,readerWait 为 0,就表示当前没有正在准备读的 goroutine 这时候调用 runtime_Semrelease
唤醒写操作。
func (rw *RWMutex) rUnlockSlow(r int32) {// A writer is pending.if atomic.AddInt32(&rw.readerWait, -1) == 0 {// The last reader unblocks the writer.runtime_Semrelease(&rw.writerSem, false, 1)}
}
写锁的加锁与释放。
func (rw *RWMutex) Lock() // 加写锁
func (rw *RWMutex) Unlock() // 释放写锁
加写锁
const rwmutexMaxReaders = 1 << 30
func (rw *RWMutex) Lock() {// 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_Semacquire(&rw.writerSem)}
}
首先调用互斥锁的 lock,获取到互斥锁之后,如果计算之后当前仍然有其他 goroutine 持有读锁,那么就调用 runtime_SemacquireMutex
休眠当前的 goroutine 等待所有的读操作完成
这里readerCount 原子性加上一个很大的负数,是防止后面的协程能拿到读锁,阻塞读
释放写锁
func (rw *RWMutex) Unlock() {// Announce to readers there is no active writer.r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)// Unblock blocked readers, if any.for i := 0; i < int(r); i++ {runtime_Semrelease(&rw.readerSem, false)}// Allow other writers to proceed.rw.w.Unlock()
}
解锁的操作,会先调用 atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
将恢复之前写入的负数,然后根据当前有多少个读操作在等待,循环唤醒
注意点:
-
读锁或写锁在 Lock() 之前使用 Unlock() 会导致 panic 异常。
-
使用 Lock() 加锁后,再次 Lock() 会导致死锁(不支持重入),需 Unlock() 解锁后才能再加锁。
-
锁定状态与 goroutine 没有关联,一个 goroutine 可以 RLock(Lock),另一个 goroutine 可以 RUnlock(Unlock)。
互斥锁和读写锁的区别:
-
读写锁区分读者和写者,而互斥锁不区分。
-
互斥锁同一时间只允许一个线程访问该对象,无论读写;读写锁同一时间内只允许一个写者,但是允许多个读者同时读对象。
相关文章:
【Golang 面试 - 进阶题】每日 3 题(二)
✍个人博客:Pandaconda-CSDN博客 📣专栏地址:http://t.csdnimg.cn/UWz06 📚专栏简介:在这个专栏中,我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话,欢迎点赞👍收藏…...

Java中等题-多数元素2(力扣)【摩尔投票升级版】
给定一个大小为 n 的整数数组,找出其中所有出现超过 ⌊ n/3 ⌋ 次的元素。 示例 1: 输入:nums [3,2,3] 输出:[3] 示例 2: 输入:nums [1] 输出:[1]示例 3: 输入:num…...
100条超牛的DOS命令
目录 1. 文件和目录管理 1.1 列出文件和目录 1.1.1 dir 1.1.2 dir /w 1.2 切换目录 1.2.1 cd 1.2.2 cd .. 1.3 创建和删除目录 1.3.1 md / mkdir 1.3.2 rd / rmdir 1.4 文件操作 1.4.1 del / erase 1.4.2 copy 1.5 文件重命名 1.5.1 ren / rename 1.5.2 move …...

大数据信用报告查询会不会留下查询记录?怎么选择查询平台?
最近有不少网友都在咨询一个问题,那就是大数据信用报告查询会不会留下查询记录,会不会对自己的征信产生影响,下面本文就详细为大家介绍一下,希望对你了解大数据信用有帮助。 首先、大数据信用与人行征信是独立的 很多人只知道人行…...

JS【详解】内存泄漏(含泄漏场景、避免方案、检测方法),垃圾回收 GC (含引用计数、标记清除、标记整理、分代式垃圾回收)
内存泄漏 在执行一个长期运行的应用程序时,应用程序分配的内存没有被释放,导致可用内存逐渐减少,最终可能导致浏览器崩溃或者应用性能严重下降的情况,即 JS 内存泄漏 可能导致内存泄漏的场景 不断创建全局变量未及时清理的闭包&…...

第三期书生大模型实战营之Llamaindex RAG实践
基础任务 任务要求:基于 LlamaIndex 构建自己的 RAG 知识库,寻找一个问题 A 在使用 LlamaIndex 之前InternLM2-Chat-1.8B模型不会回答,借助 LlamaIndex 后 InternLM2-Chat-1.8B 模型具备回答 A 的能力,截图保存。 streamlit界面…...
【从0到1进阶Redis】Jedis 理解事务
笔记内容来自B站博主《遇见狂神说》:Redis视频链接 小伙伴们可以熟悉一下本专栏的 Redis 文章,可以更好地理解 正常操作 package oldfe.study;import com.alibaba.fastjson.JSONObject; import redis.clients.jedis.Jedis; import redis.clients.jedis.T…...
MySQL之Lost connection to MySQL server during query复现测试
测试Lost connection to MySQL server during query复现条件 环境报错信息复现测试方式一方式二 环境 Python: 3.8/3.9 Mysql: 5.x 报错信息 File "/Users/xxx/lib/python3.9/site-packages/sqlalchemy/dialects/mysql/base.py", line 2509, in do_rollbackdbapi_con…...

中国AI大模型场景探索及产业应用调研报告
AI大模型发展态势 定义 AI大模型是指在机器学习和深度学习领域中,采用大规模参数(至少在一亿个以上)的神经网络模型,AI大模型在训练过程中需要使用大量的算力和高质量的数据资源。 产业规模 2023年,中国大模型市场规模为147亿。结合《202…...

Linux--shell脚本语言—/—<1>
一、shell简介 Shell是一种程序设计语言。作为命令语言,它交互式解释和执行用户输入的命令或者自动地解释和执行预先设定好的一连串的命令;作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构&am…...
【java框架开发技术点】通过反射机制调用类中的私有或受保护的方法
示例 假设我们有一个类 ExampleClass,其中有一个私有方法 privateMethod: public class ExampleClass {private void privateMethod(String message) {System.out.println("Private method called with message: " + message);} }我们可以使用上述代码来调用这个…...
你知道这些鼎鼎大名的Java底层核心公司吗
在讨论Java虚拟机——JVM的时候,有几个知名的,不得不提到的JVM的产品和公司。 一、Oracle HotSpot:这是由Sun公司开发的虚拟机。它由最初的Classic VM开始,到推出崭露头角的Exact VM的虚拟机,是现代化高性能虚拟机的最…...

C++入门级文章
一、一个用于查询C标准库内函数、操作符等的链接 https://legacy.cplusplus.com/reference/ 声明:该文档并非官方文档,但其具有易于查询和使用的优势,足够日常使用。 二、C的第一个程序 1、C语言中的语法在C中仍旧适用,首先我们来…...

modelsim仿真quartus IP
开发环境:quartus prime pro 20;modelsim se-64 10.6d 1. 生成Altera的IP库 使用quartus生成IP库,需要使用Simulation Library Compiler(Tools->Launch Simulation Library Compiler) 如下图操作,选择…...

PCB设计经验——布线原则
1.连线精简——避免直角布线 导线也应看作一种元器件,有自己的电阻,电感,电容 PCB走线在直角转弯的地方,信号前后部分相互影响,导致分布电容增加,对信号上升沿和下降沿有延缓影响。从阻抗的角度来说&#…...

C++进阶:设计模式___适配器模式
前言 在C的基础语法的学习后,更进一步为应用场景多写代码.其中设计模式是有较大应用空间. 引入 原本在写容器中适配器类有关的帖子,发现适配模式需要先了解,于是试着先写篇和适配器模式相关的帖子 理解什么是适配器类,需要知道什么是适配器模式.适配器模式是设计模式的一种.笔…...

“八股文“在现代编程面试中的角色重塑:助力、阻力还是桥梁?
🌈所属专栏:【其它】✨作者主页: Mr.Zwq✔️个人简介:一个正在努力学技术的Python领域创作者,擅长爬虫,逆向,全栈方向,专注基础和实战分享,欢迎咨询! 您的点…...
Android 安装应用-浏览阶段
应用安装的浏览阶段主要是由PackageManagerService类中的scanPackageNewLI()实现的,看一下它的代码: // TODO: scanPackageNewLI() and scanPackageOnly() should be merged. But, first, commiting// the results / removing app data needs to be move…...

JavaEE 初阶(10)——多线程8之“单例模式”
目录 一. 设计模式 二. 单例模式 2.1 饿汉模式 2.2 懒汉模式 a. 加锁synchronized b. 双重if判定 c. volatile关键字(双重检查锁定) 一. 设计模式 设计模式是在软件工程中解决常见问题的经典解决方案。针对一些特定场景给出的一些比较好的解决…...
Javascript常见设计模式
JS设计模式学习【待吸收】-CSDN博客 JavaScript 中的设计模式是用来解决常见问题的最佳实践方案。这些模式有助于创建可重用、易于理解和维护的代码。下面列出了一些常见的 JavaScript 设计模式及其代码示例。 1. 单例模式(Singleton) 单例模式确保一…...

【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...

STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...

Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...

c++第七天 继承与派生2
这一篇文章主要内容是 派生类构造函数与析构函数 在派生类中重写基类成员 以及多继承 第一部分:派生类构造函数与析构函数 当创建一个派生类对象时,基类成员是如何初始化的? 1.当派生类对象创建的时候,基类成员的初始化顺序 …...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...