当前位置: 首页 > news >正文

Golang学习-sync.RWMutex 读写锁

sync.RWMutex 读写锁


一、读写锁解决了什么问题

普通的 Mutex 不管操作是读还是写,一律互斥。但很多场景下,读操作远多于写操作,读操作之间本可以安全并发——它们不会修改数据。

RWMutex 提供两种锁:

  • 读锁(RLock):多个 goroutine 可以同时持有,彼此不阻塞
  • 写锁(Lock):独占,持有期间所有读锁和写锁都被阻塞

这一设计显著提升了读多写少场景的并发性能。

二、基本用法

var(mu sync.RWMutex balanceint64)// 读操作:使用读锁funcBalance()int64{mu.RLock()defermu.RUnlock()returnbalance}// 写操作:使用写锁(和 Mutex 一样)funcDeposit(amountint64){mu.Lock()defermu.Unlock()balance+=amount}

命名约定:Lock/Unlock 用于写,RLock/RUnlock 用于读。

三、锁的互斥规则

已有读锁已有写锁
请求读锁✅ 允许❌ 阻塞
请求写锁❌ 阻塞❌ 阻塞

核心理解:读读不互斥,其他都互斥

四、适用场景判断

RWMutex 不是万能的。它的内部维护了更复杂的记账信息,无竞争时比 Mutex 稍慢。只有在以下条件同时满足时,RWMutex 才有优势:

  1. 读操作占绝大多数
  2. 存在锁竞争(多个 goroutine 同时在等锁)

如果没有竞争,Mutex 更轻更快;如果写操作频繁,RWMutex 不但无益,还会因为写锁不断阻塞读锁而导致性能下降。

五、读锁不能升级为写锁

这是一个经典的坑:你不能先 RLock,然后在持有读锁的情况下再 Lock。

mu.RLock()// ... 发现需要修改数据 ...mu.Lock()// 死锁!写锁在等待读锁释放,读锁永远不会释放

正确的做法是先 RUnlock,再 Lock,并且重新检查条件(因为释放读锁到获取写锁之间,数据可能已被其他 goroutine 改变):

mu.RLock()ifneedUpdate{mu.RUnlock()mu.Lock()// 重新检查!可能在等写锁期间别人已经改过了ifneedUpdate{/* 执行更新 */}mu.Unlock()}else{mu.RUnlock()}

六、不要假设"只读"就一定安全

即使一个方法看起来只读取数据,它也可能悄悄修改内部状态(比如更新访问计数器、填充缓存)。如果不确定,用 Mutex 更安全

七、练习代码

保存为rwmutex_demo.go

packagemainimport("fmt""sync""time")// 一个简单的配置中心:读多写少typeConfigstruct{mu sync.RWMutex datamap[string]string}funcNewConfig()*Config{return&Config{data:map[string]string{"version":"1.0","env":"dev"},}}// 读配置(允许并发读)func(c*Config)Get(keystring)string{c.mu.RLock()deferc.mu.RUnlock()returnc.data[key]}// 写配置(独占)func(c*Config)Set(key,valuestring){c.mu.Lock()deferc.mu.Unlock()c.data[key]=value}// 升级演示:先读后决定是否写func(c*Config)SetIfChanged(key,newValuestring)bool{c.mu.RLock()oldValue:=c.data[key]c.mu.RUnlock()ifoldValue==newValue{returnfalse// 值相同,不写}// 获取写锁,重新检查c.mu.Lock()deferc.mu.Unlock()// 重新检查:等写锁期间可能已被别人修改ifc.data[key]==newValue{returnfalse}c.data[key]=newValuereturntrue}funcmain(){cfg:=NewConfig()varwg sync.WaitGroup// 模拟 100 个并发读fori:=0;i<100;i++{wg.Add(1)gofunc(idint){deferwg.Done()v:=cfg.Get("version")_=v// 实际使用}(i)}// 模拟 5 个并发写fori:=0;i<5;i++{wg.Add(1)gofunc(idint){deferwg.Done()cfg.Set(fmt.Sprintf("key_%d",id),fmt.Sprintf("val_%d",id))}(i)}// 模拟升级锁模式fori:=0;i<10;i++{wg.Add(1)gofunc(idint){deferwg.Done()cfg.SetIfChanged("version","1.0")}(i)}wg.Wait()c.mu.RLock()fmt.Printf("配置条目数: %d\n",len(c.data))fmt.Printf("version: %s\n",c.data["version"])c.mu.RUnlock()}

运行结果:

配置条目数: 7 version: 1.0

八、关键要点

要点说明
RLock/RUnlock读锁,允许多个 goroutine 同时持有
Lock/Unlock写锁,独占,阻塞所有读写
不可升级不能从读锁升级为写锁,必须先释放再获取
重新检查释放读锁到获取写锁之间数据可能变化,需 double-check
适用场景读多写少 + 存在锁竞争
无竞争时Mutex 比 RWMutex 更轻更快
http://www.cnnetsun.cn/news/3525674.html

相关文章:

  • 物理AI迎来“开悟时刻”:大晓发布开悟世界模型、以人为中心的环采方案2.0与三大行业解决方案
  • 炸裂!WhatsApp再诉NSO:一纸“永久禁令”为何沦为废纸?全球“合法监听”黑产链调查
  • 金额用 Long 还是 BigDecimal?
  • grunt-contrib-copy 配置文件详解:从基础配置到高级选项
  • 【限时解密】头部MCN私有数字人直播中台架构图:含ASR延迟<200ms的FPGA加速方案与商用授权清单
  • fluxsort基准测试:在不同数据集上的表现分析
  • KnpGaufretteBundle社区贡献指南:如何参与项目开发与维护
  • Bazel rules_foreign_cc快速入门:5分钟学会构建外部C/C++项目
  • Verilog飞行射击游戏设计与实现:从敌机生成到子弹碰撞
  • 程序员誓言Programmer‘s Oath:保护用户隐私的7个关键原则
  • kableExtra在企业级应用中的应用:构建自动化报表系统的完整方案
  • ES6-learning:模块化编程的终极指南 - 掌握现代JavaScript开发的核心技能
  • McBSP多通道选择与帧同步:TDM通信原理与实战配置详解
  • physx-rs源码探秘:Rust绑定NVIDIA PhysX的实现原理
  • Slug性能优化技巧:提升URL短链服务响应速度的10个方法
  • 英雄联盟Akari助手:免费开源的终极游戏效率工具,快速提升你的操作水平
  • HarmonyOS7 同级分组菜单:用 MenuItemGroup 把菜单分层做得更顺手
  • Unity安卓打包全攻略:从环境配置到自动化构建的实战指南
  • TVA与世界模型共创具身智能“类脑想象力”(7)
  • 先计划,再动刀,Claude Code 的 plan 模式为什么适合严肃工程项目
  • MOSS-Music-8B-Thinking-6bit核心组件详解:音频编码器与语言模型融合
  • HarmonyOS7 跨层购物车页实战:@Provide 与 @Consume 不只是会用,还要用得顺手
  • Apache Commons Collections 与Java Stream API:如何选择最佳数据处理方案
  • 067、3A联动与协同控制:曝光、对焦、白平衡的闭环优化
  • 千问里面的表格怎么复制?AI 导出鸭安卓版一键无损导出完整表格
  • 数字人导游商业化闭环模型(已跑通12家5A景区,ROI达1:5.8的3层变现架构与License授权陷阱预警)
  • 吃透这 17 个概念,比 95% 的开发者更懂 AI
  • 【2018-10-23】Linux下生成c/c++项目代码覆盖率的产生方法
  • 第3天:用C语言重写点灯程序
  • A Unified Representation Underlying the Judgment of Large Language Models