Go RWMutex 读锁很多时写操作如何获得机会
来源:17golang原创
时间:2026-09-09 10:51:21 367浏览 收藏
读多写少的配置、路由表或本地缓存经常会用到 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 的描述可以作为最终判断依据:它允许任意数量的读者或一个写者;写者等待时,新的读锁会阻塞,直到写者获得并释放锁。
-
443 收藏
-
245 收藏
-
109 收藏
-
291 收藏
-
417 收藏
-
477 收藏
-
312 收藏
-
436 收藏
-
396 收藏
-
426 收藏
-
152 收藏
-
479 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习