当前位置:首页 > 文章列表 > Golang > Go问答 > 读写锁

读写锁

来源:17golang原创 2026-11-11 16:32:37 0浏览 收藏

Go 的 sync.RWMutex 没有“把读锁升级成写锁”的操作。持有 RLock 时直接调用 Lock,当前 goroutine 会等所有读者退出,而它自己还没有调用 RUnlock,于是形成等待环。处理读后写场景时,应先释放读锁,再获取写锁,并在写锁内重新检查一次状态。

要点速览
  • RLock 和 Lock 是两种互斥状态,官方语义不支持直接升级。
  • 正确顺序是:读锁判断、释放读锁、写锁重查;不能把第一次读取的结果直接当成最终结论。
  • 是否使用 RWMutex 要看读写比例、锁等待时间和实际基准,不是锁名字里有“读”就一定更快。

为什么持有 RLock 时再 Lock 会卡住

RWMutex 允许多个读者同时进入,但同一时刻只能有一个写者。它还要保证写者最终能获得机会:当某个 goroutine 已经等待 Lock 时,后续新的 RLock 会被挡住,直到写者完成。

问题在于“升级”并不是把状态上的一个标记改掉。下面的反例在发现缓存缺失后尝试写入:

func (c *Cache) BrokenGet(key string) string {
	c.mu.RLock()
	defer c.mu.RUnlock() // 读锁还没释放,函数返回前才会执行
	if value, ok := c.items[key]; ok {
		return value
	}
	c.mu.Lock() // 这里等待所有读者退出,其中包括当前 goroutine
	defer c.mu.Unlock()
	c.items[key] = loadValue(key)
	return c.items[key]
}

这里不是“偶尔竞争失败”,而是锁的生命周期自相矛盾:Lock 要等当前读者减少,当前 goroutine 却要等 Lock 返回后才有机会执行 RUnlock。Go 官方文档因此明确写出,RLock 不能升级为 Lock,Lock 也不能降级为 RLock。

Go sync.RWMutex 中 RLock、RUnlock、Lock 和等待写者之间的静态关系,展示读锁升级形成的自等待环
图1:读锁与写锁的静态边界说明图;持有 RLock 时直接请求 Lock 会把当前读者留在写者等待条件中。

读后写场景要用“释放、获取、重查”

常见需求不是“升级锁”,而是先用便宜的读锁查一次,只有缺失时才修改共享数据。此时第一次读取只是线索,不能跨越解锁阶段直接复用。一个可控的缓存写入方式如下:

type Cache struct {
	mu    sync.RWMutex
	items map[string]string
}

func (c *Cache) GetOrLoad(key string, load func(string) string) string {
	c.mu.RLock()
	value, ok := c.items[key]
	c.mu.RUnlock() // 先结束读临界区,不能用 defer 把它拖到写锁之后
	if ok {
		return value
	}

	c.mu.Lock()
	defer c.mu.Unlock() // 写入和第二次检查必须处在同一个临界区
	if value, ok = c.items[key]; ok {
		return value // 其他 goroutine 可能已经在空档期完成写入
	}
	value = load(key) // 回调不要再次调用会获取 c.mu 的方法
	if c.items == nil {
		c.items = make(map[string]string)
	}
	c.items[key] = value
	return value
}

第二次检查是这段代码的关键。当前 goroutine 释放读锁到重新获取写锁之间,其他 goroutine 可能已经填充了同一个键;不重查就会重复加载,甚至覆盖更新后的值。若 load 很慢,还应把耗时工作移到锁外,并为结果设计版本或单飞协调机制,避免把慢回调放在写锁里。

Go Cache、RLock、RUnlock、Lock、二次检查和 map 写入之间的静态关系说明图
图2:读后写的结构说明图;第一次读取只负责判断,写锁保护二次检查与 map 更新。

怎样用指标判断 RWMutex 是否值得保留

读写锁的优势来自读者之间可以并行,而不是来自 API 名称。先记录访问路径的读写比例,再分别观察锁等待时间、临界区耗时和整体吞吐;如果写请求频繁、临界区很短,Mutex 可能更简单也更快。

观察项怎么记录它能回答什么
读写比例分别统计 RLock 路径与 Lock 路径的请求数是否存在足够的并行读机会
锁等待在进入临界区前后记录等待时长分布瓶颈是竞争还是临界区逻辑
基准结果固定数据规模,比较 RWMutex 与 Mutex 的 ns/op、allocs/op改锁是否真的改善当前负载
# 只运行基准,不运行普通测试;基准函数应在同一数据规模下比较两种锁
go test -run '^$' -bench='BenchmarkCache' -benchmem ./...

# 多次运行降低单次调度噪声,结果仍要结合业务读写比例解释
go test -run '^$' -bench='BenchmarkCache' -benchmem -count=5 ./...

不要把一次本地 ns/op 直接当成线上结论。基准中要固定 goroutine 数、键分布、命中率和回调耗时;线上则继续看 p95/p99 等等待分位数。这样才能区分“读者并行带来的收益”和“锁内工作变少带来的收益”。

这几个边界容易把问题带回原点

  • 递归读锁:看似只是再次读取,但待写者会阻塞新的读者,递归调用可能永远等不到返回。
  • 锁内回调:回调如果再次访问同一对象并获取同一把锁,会形成隐式自等待;把回调契约写清楚。
  • 跨 goroutine 解锁:RWMutex 不绑定 goroutine,但跨协程交接会让生命周期难以审查,除非所有权边界非常明确。
  • 复制锁对象:RWMutex 第一次使用后不能复制;包含它的结构体也要避免值拷贝。

相关问题

释放 RLock 后还需要重新读取吗?

如果后续要写共享状态,需要重新检查。解锁期间状态可能变化,第一次读到的结果只代表当时的观察。

能不能先 RUnlock,再马上 Lock?

可以,这是避免等待环的必要步骤,但它不是原子升级;在两次调用之间,其他 goroutine 可能先完成写入。

读多写少就一定该用 RWMutex 吗?

不一定。只有在读者并行收益能抵消更复杂的状态维护和竞争成本时才值得采用,最终用固定负载基准确认。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
并发计数复用并发计数复用
上一篇
并发计数复用
互斥锁复制
下一篇
互斥锁复制
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    982次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    986次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    1105次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    1075次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    916次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码