当前位置:首页 > 文章列表 > Golang > Go问答 > Go RWMutex 读锁升级为写锁为什么会死锁

Go RWMutex 读锁升级为写锁为什么会死锁

来源:17golang原创 2026-09-12 15:57:33 0浏览 收藏

遇到 Go 里的“读锁升级死锁”,先看调用顺序:如果同一个 goroutine 已经执行了 RLock(),又在没有执行 RUnlock() 的情况下调用 Lock(),它会一直等下去。原因不是写锁性能慢,而是 sync.RWMutex 没有把读锁原子升级为写锁的语义;写锁必须等所有读锁离开,当前 goroutine 却正占着其中一个读锁。

安全原则是:读阶段只观察并保存快照,先释放读锁;需要修改时再申请写锁,并在写锁内重新检查条件。若业务要求“检查和更新”不可被插入,就把这段状态放进单独的 Mutex 或专门的状态锁中。
要点速览
  • RLock 后直接 Lock 不是升级,而是互相等待。
  • 释放读锁后重取写锁时,必须接受状态可能已经变化,并重新确认条件。
  • 锁的复制、漏解锁和把外部调用放进临界区,都会把问题扩大。

为什么 RLock 后再 Lock 会卡住

下面的最小示例只用来说明等待关系,不需要在生产代码中保留。Lock 的调用点永远等不到返回,所以后面的 Unlock 也永远不会执行。

package main

import "sync"

func upgrade(mu *sync.RWMutex) {
	mu.RLock()
	defer mu.RUnlock() // 读阶段原本会在函数返回时释放读锁

	mu.Lock() // 错误:写锁等待所有读锁,包括当前持有的读锁
	defer mu.Unlock() // 只有 Lock 返回后才有机会登记写锁释放
}

把现象拆成两个条件就清楚了:RLock 允许多个读者同时进入;Lock 需要排除读者。当前 goroutine 不能“暂时保留读锁并把自己算作例外”,也不能靠再次调用 RLock 来完成升级。

调用状态锁的实际要求结果
已经持有 RLock当前读者仍在锁内可以继续读取,但不能升级
调用 Lock等待全部读锁释放当前 goroutine 被挂起
准备 RUnlock需要先走过 Lock 返回点形成自等待
Go sync.RWMutex 读锁升级写锁时当前 goroutine 与全部读锁释放条件形成等待关系的静态示意图
图1:读锁升级死锁的静态关系示意;当前 goroutine 必须释放 RLock 才能满足 Lock 的条件,但它又在等待 Lock 返回。

修复:释放读锁后重新获取写锁

最常见的改法是把读写阶段分开。读锁内只取出足够判断的快照,离开读锁后再申请写锁;由于等待期间别的 goroutine 可能已经完成更新,写锁内必须再次检查。

func (c *Cache) PutIfMissing(key, value string) bool {
	c.mu.RLock()
	_, exists := c.items[key] // 读锁只负责读取共享 map
	c.mu.RUnlock()
	if exists {
		return false
	}

	c.mu.Lock()
	defer c.mu.Unlock() // 写路径统一负责释放写锁
	if _, exists = c.items[key]; exists {
		return false // 重取写锁后复核,避免覆盖先到的结果
	}
	c.items[key] = value
	return true
}

这段写法解决的是死锁,不等于把整个“检查—写入”变成了无竞争事务。真正的原子性由写锁内的第二次检查保证。若检查需要调用数据库、网络或回调,不要把这些慢操作放进锁里;先取得结果,再用短临界区提交状态。

Go 缓存共享状态从读快照到 RUnlock 再到写锁条件复核的静态边界示意图
图2:安全更新方案的静态边界示意;读阶段只产生快照,写阶段重新确认条件后更新共享状态。

什么时候应该改用 Mutex 或单独状态锁

如果业务真正需要的是“判断状态并立刻更新”,读锁带来的并发读取收益可能不值得承担两阶段重检。此时用普通 sync.Mutex 把状态转换包起来,通常更容易证明正确。

func (c *Cache) PutIfMissing(key, value string) bool {
	c.mu.Lock()
	defer c.mu.Unlock() // 检查与写入共享同一把互斥锁
	if _, exists := c.items[key]; exists {
		return false
	}
	c.items[key] = value
	return true
}

还可以把“高频只读数据”和“需要原子变更的状态”拆开:前者由 RWMutex 保护,后者由独立 Mutex 保护。关键不是锁的名字,而是让一个不变量只由一条清晰的锁边界维护。

排查时别漏掉这几个锁坑

  • defer 位置太晚:defer RUnlock() 只适合保护整个读临界区;如果后面还要申请写锁,应在申请写锁前显式释放。
  • 复制含锁的结构体:RWMutex 不应在首次使用后复制,缓存对象应通过指针或构造后固定的实例共享。
  • 错误的解锁配对:RLock 对应 RUnlockLock 对应 Unlock,混用会触发运行时错误。
  • 把外部工作放进临界区:网络、磁盘和回调时间不可控,会让等待被误判成升级死锁。

修复后可以给锁竞争路径加一个有限超时的测试 harness,再用 go test -race 检查数据竞争。竞态检测器不能证明没有死锁,但能发现另一类“先读后写未受保护”的问题;死锁判断仍要靠清晰的锁顺序和可退出测试。

相关问题

RWMutex 能不能在 RLock 后先 RUnlock 再 Lock?

可以,这是常见的分阶段写法,但两次调用之间状态可能变化,因此写锁内必须重新检查条件。

多个 goroutine 都想从读锁升级会怎样?

它们都可能持有读锁并等待写锁,而写锁又等待读锁清空,形成整体停顿。不要把升级逻辑放进共享的读路径。

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

不一定。若临界区很短、状态转换很多或证明成本较高,普通 Mutex 往往更直接;先按不变量和实际竞争选择。

结论

Go sync.RWMutex 的读锁和写锁是两种互斥语义,不提供升级通道。看到“RLock 后 Lock 卡住”,先检查是否让当前读者等待自己退出;然后把读快照、释放读锁、写锁复核和状态更新分成清楚的边界。需要原子检查更新时,直接使用 Mutex 或独立状态锁,通常比模拟升级更稳。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
MySQL 死锁日志看不懂时先找哪两条事务MySQL 死锁日志看不懂时先找哪两条事务
上一篇
MySQL 死锁日志看不懂时先找哪两条事务
Redis LRU 和 LFU 淘汰策略如何按访问特征选择
下一篇
Redis LRU 和 LFU 淘汰策略如何按访问特征选择
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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推荐
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    102次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    16次使用
  • OpenCompass大模型评测体系详解:功能、使用指南与应用场景
    OpenCompass
    OpenCompass是上海AI实验室推出的开源大模型评测平台,提供CompassKit、CompassHub和CompassRank三大核心组件,支持LLM及多模态模型的一站式标准化评估与排行榜查询。
    29次使用
  • AGI-Eval大模型评测平台:权威榜单、数据集与人机协同评测方案
    AGI-Eval
    AGI-Eval是由上海交大等高校联合发布的大模型评测社区,提供公正透明的LLM能力榜单、多领域评测集及Data Studio数据服务,助力AI模型性能评估与NLP科研开发。
    17次使用
  • SuperCLUE中文大模型评测基准:功能、能力维度与应用指南
    SuperCLUE
    SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
    257次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码