应仅在读锁持有时间极短、并发读 goroutine 极多、写操作极度稀疏(如分钟级配置更新)时使用 sync.RWMutex;否则优先选 sync.Mutex 或 atomic.Value。

sync.RWMutex 只在读远多于写、且读操作本身不耗时的场景下才值得用;写操作一多,它比 sync.Mutex 还慢。
什么时候该用 sync.RWMutex 而不是 sync.Mutex
核心判断依据不是“读比写多”,而是:RLock() 持有时间是否足够短、并发读 goroutine 是否足够多、写操作是否真的稀疏(比如每分钟一次配置热更新)。
- 适用:内存缓存(
map[string]*User)、只读路由表、静态配置快照 - 不适用:高频写入的计数器、带网络调用的读逻辑、单 goroutine 访问、值类型(
int/string)字段 - 反模式:用
Lock()做纯读操作 → 完全浪费读并发能力 - 性能陷阱:写操作每秒超 10 次,
RWMutex的读锁计数开销就开始拖累整体吞吐
RLock() 和 RUnlock() 必须严格成对,且不能跨分支提前 return
漏掉一次 RUnlock(),所有后续 Lock() 就永远卡住;多调一次直接 panic:sync: RUnlock of unlocked RWMutex。
- 正确写法:紧挨着
RLock()就加defer mu.RUnlock(),哪怕函数只有两行 - 错误写法:
mu.RLock()在 if 外,但return在 if 内 →RUnlock()永远不执行 - 别在循环体里反复
RLock()/RUnlock()—— 锁粒度太细,极易配对失败 - 调试建议:用
go build -race运行,race detector 能捕获大部分配对问题
嵌入结构体时,接收器必须是指针
如果结构体字段是 mu sync.RWMutex,但方法签名是 func (s MyStruct) Read(),那 s.mu.RLock() 操作的是 mu 的副本 —— 零保护作用,且毫无报错。
立即学习“go语言免费学习笔记(深入)”;
- 必须写成
func (s *MyStruct) Read() -
sync.RWMutex是零值可用类型,无需显式new()或&sync.RWMutex{} - 别把它和
sync.Mutex混用在同一临界区,除非你明确定义了全局锁获取顺序(比如“总是先RWMutex后Mutex”)
写锁会阻塞新读请求,但不打断已有读操作
这是最常被误解的一点:Lock() 等待的是“所有已持有的读锁被释放”,而不是“当前读操作执行完”。一个长耗时读操作(比如遍历大 map + HTTP 调用)持着 RLock(),写就会一直等,连带后续所有新读也被堵住。
- 解决思路:把纯内存读取部分锁住,IO 或计算移出
RLock()临界区 - 别在
RLock()里发 HTTP 请求、查数据库、收 channel —— 这等于把锁变成全局瓶颈 - 若业务无法容忍写饥饿,考虑换
atomic.Value做无锁快照,或加 context 超时控制写锁获取
真正难的不是怎么写 RLock(),而是判断这段数据到底该不该上读写锁 —— 有时候删掉锁、改用 channel 或拆成不可变快照,反而更简单可靠。


















