Go RWMutex 读锁很多时写操作如何获得机会
读多写少的配置、路由表或本地缓存经常会用到 sync.RWMutex。读锁很多时,写操作并不是靠“抢跑”获得机会:已经拿到 RLock 的读者先完成,写者调用 Lock 后,后续新的 RLock 会被挡住,等现有读者退出后让写者进入。
所以 Go 的 RWMutex 具备防止写者长期饥饿的排队语义,但不是严格的读写 FIFO 调度器;写者能获得机会,不等于每个请求都有固定顺序。
- 写者等待期间,新的读者不能继续加入当前读者组。
- 读方法用
RLock/RUnlock,更新方法用Lock/Unlock,锁不能复制。 - 不要在持有读锁时再升级写锁;慢计算应放在锁外,锁内只做快照读写。
把读多写少配置做成一个小项目
先做一个极小的配置存储:HTTP 请求频繁读取 timeout,后台任务偶尔把它改成新值。关键不是把所有方法都加锁,而是让共享 map 的每次访问都落在正确的锁范围内。
package main
import (
"fmt"
"sync"
)
// ConfigStore 保存可被多个 goroutine 读取的配置快照。
type ConfigStore struct {
mu sync.RWMutex
data map[string]string
}
// Get 只读取共享 map,因此使用读锁。
func (s *ConfigStore) Get(key string) (string, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
value, ok := s.data[key]
return value, ok
}
// Set 修改共享 map,必须独占整个更新区间。
func (s *ConfigStore) Set(key, value string) {
s.mu.Lock()
defer s.mu.Unlock()
s.data[key] = value
}
func main() {
store := &ConfigStore{data: map[string]string{"timeout": "800ms"}}
store.Set("timeout", "1s")
value, ok := store.Get("timeout")
fmt.Println(value, ok) // 这里会读到一次完整的配置值
}
这里的 defer 放在成功加锁之后,能保证每个出口都解锁。RWMutex 本身不要按值复制;把它作为结构体字段,并通过指针接收者调用,是更稳妥的组织方式。
| 调用 | 可并发对象 | 关键边界 |
|---|---|---|
RLock | 多个读者 | 只读共享状态,结束后必须 RUnlock |
Lock | 单个写者 | 等待读者退出,更新期间排斥读写 |
| 读转写 | 不支持升级 | 先释放读锁再重新申请写锁,期间状态可能变化 |
写者等待时读锁为什么会让出通道
假设读者 R1、R2 已经拿到读锁,此时写者 W 调用 Lock。W 不能打断 R1、R2,但它一旦进入等待,之后到达的 R3、R4 不能再通过 RLock 加入。R1、R2 依次 RUnlock 后,W 获得独占锁并完成更新,W 释放后,排队的读者才继续。

这正是“写者如何获得机会”的答案。它不是承诺 W 一定紧接着某个读者执行,也不是按到达时间给所有 goroutine 排号,而是先阻断新的读者,避免读者流持续增长把写者压在队列末尾。
防止把写者机会误解成严格公平
RWMutex 的公平性应理解为边界规则,而不是调度承诺:
- 已经持有的读者不会被强行踢出;读临界区越长,写者等待越久。
- 写者一旦等待,新的读者会阻塞,但唤醒顺序仍不应被业务代码当成稳定的 FIFO。
- 读锁不能递归依赖。一个 goroutine 持有读锁后再等待写者完成,可能形成自我等待。
因此,读操作里不要做网络请求、磁盘扫描或长时间计算。先在锁内复制必要字段,再在锁外慢慢处理,通常更容易让写者及时穿过临界区。

一个实用的排查清单
遇到“写操作偶尔很慢”时,先检查四件事:读者是否把外部调用放在 RLock 内;是否存在忘记 RUnlock 的分支;写者是否在锁内做了序列化或 I/O;是否有人试图先读后写而没有释放读锁。若写入频率已经很高,或者读写内容可以通过消息传递串行化,通道或不可变快照也可能比 RWMutex 更适合。
常见问题
读锁很多时,新的读请求会永远失败吗?
不会。它们通常只是等待写者完成;写者释放后,排队的读者可以继续获取读锁。
能否在 RLock 后直接调用 Lock?
不能把它当作升级操作。先调用 RUnlock,再调用 Lock,但这两个动作之间可能有其他写入。
RWMutex 是否比 Mutex 一定快?
不一定。只有读并发明显、读临界区短且写入相对少时,多个读者并行才可能抵消额外管理成本,应结合实际竞争情况评估。
官方文档对 RWMutex 的描述可以作为最终判断依据:它允许任意数量的读者或一个写者;写者等待时,新的读锁会阻塞,直到写者获得并释放锁。
Go 1.27 原生 uuid 包如何改变项目依赖选择
- 上一篇
- Go 1.27 原生 uuid 包如何改变项目依赖选择
- 下一篇
- PHP match 表达式没有匹配分支时如何设计默认结果
-
- Golang · Go问答 | 27分钟前 | 并发 · go · sync.Mutex ·
- Go sync.Mutex 应该嵌入值还是通过指针接收者使用
- 443浏览 收藏
-
- Golang · Go问答 | 32分钟前 |
- Go goroutine 里 range channel 不退出通常缺哪一步
- 245浏览 收藏
-
- Golang · Go问答 | 34分钟前 |
- Go time.AfterFunc 回调和主 goroutine 如何安全协作
- 109浏览 收藏
-
- Golang · Go问答 | 2小时前 |
- Go select 中把 nil channel 切换为有效 channel 怎么动态启用分支
- 477浏览 收藏
-
- Golang · Go问答 | 2小时前 |
- Go 关闭带缓冲 channel 后为什么还能读完剩余元素
- 436浏览 收藏
-
- Golang · Go问答 | 2小时前 | map · Slice · go · comparable ·
- Go map 的 key 为什么不能直接使用切片
- 396浏览 收藏
-
- Golang · Go问答 | 3小时前 | Go问答 · Go map · maps.Clone · nil语义 · Go Go问答 nil map maps.Clone map复制
- Go maps.Clone 传入 nil map 时返回值是什么
- 152浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 41次使用
-
- SuperCLUE
- SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
- 193次使用
-
- C-Eval
- 深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
- 129次使用
-
- AI Prompt Library
- 探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
- 60次使用
-
- Generrated
- Generrated汇集9300+张DALL·E生成图像及对应提示词,支持查看完整图集、对比DALL·E 2与3版本差异,是AI绘图新手学习Prompt设计与获取创作灵感的实用工具。
- 42次使用
-
- Go map 并发写 panic 怎么办:从共享 map 到可控写入路径
- 2026-06-30 123浏览
-
- golang 基于 mysql 简单实现分布式读写锁
- 2023-01-07 384浏览
-
- Go保证并发安全底层实现详解
- 2023-02-24 417浏览
-
- Go语言开发保证并发安全实例详解
- 2023-01-07 328浏览
-
- Golang 手写一个简单的并发任务 manager
- 2022-12-23 367浏览
