Golang Mutex与RWMutex的使用区别

12次阅读

该用 sync.Mutex 而不是 sync.RWMutex 的情况是写操作频繁或读写比例接近 1:1 甚至写更多时,因其无状态切换开销、无写饥饿风险、加解锁路径最短,且避免 RWMutex 在写多时的排队放大与死锁风险。

Golang Mutex与RWMutex的使用区别

什么时候该用 sync.Mutex 而不是 sync.RWMutex

当你需要「写操作频繁」或「读写比例接近 1:1 甚至写更多」时,sync.Mutex 更简单、更安全。它没有读写状态切换开销,也没有写饥饿风险,加锁/解锁路径最短。

  • 典型场景:bank.Account.Transfer、连接池分配器、状态机状态变更(如 isClosed 标志位更新)
  • 写多时 RWMutex 反而更慢:每次 RLock 都要检查是否有等待中的写锁,而写锁又会阻塞所有新读请求,造成排队放大
  • 如果逻辑里存在「读中判断后立刻写」的模式(比如 if x == 0 { x++ }),强行用 RWMutex 容易掉进死锁坑——后面会细说

RLockLock 混用时最常触发的死锁怎么避

最典型的错误是在持有 RLockgoroutine 里直接调用 Lock

func (c *Counter) IncrementIfZero() {     c.mu.RLock()     defer c.mu.RUnlock()     if c.value == 0 {         c.mu.Lock() // ⚠️ 死锁:自己占着读锁,又等自己释放读锁才能拿写锁         c.value++         c.mu.Unlock()     } }
  • RWMutex 不允许同一线程「读锁未放就抢写锁」,底层会一直阻塞直到所有读锁释放(包括当前 goroutine 持有的那个)
  • 正确做法是「先放读锁,再抢写锁」,并做好竞态重检(因为中间可能被其他 goroutine 修改)
  • 别依赖「同一个 goroutine 解锁」——RWMutex 允许 A goroutine 加读锁、B goroutine 解读锁,但混用极易出错,不建议

读多写少场景下,RWMutex 真的比 Mutex 快多少

实测数据(Go 1.22, 8 核)显示:当读操作占比 ≥ 70%,RWMutex 的吞吐量可高出 Mutex 2–4 倍;但一旦读占比降到 40% 以下,两者性能基本持平,甚至 RWMutex 因额外状态管理略慢。

  • 关键不是「有没有读」,而是「并发读是否真实存在」:单个 goroutine 循环读 1000 次 ≠ 并发读,这种场景用 Mutex 更轻量
  • RWMutex 内部其实嵌套了一个 sync.Mutex(用于保护写锁逻辑),所以它不是零成本的「升级版」
  • 如果你的「读」操作本身很重(比如深拷贝 map 或序列化 jsON),锁粒度再细也没用——该优化的是业务逻辑,不是换锁类型

锁变量命名和初始化的两个硬性习惯

Go 的 sync.Mutexsync.RWMutex 都是值类型,零值可用,但「首次使用后禁止拷贝」是一条铁律。很多 bug 来自结构体字段被浅拷贝或作为函数参数传值。

立即学习go语言免费学习笔记(深入)”;

  • 永远把锁变量命名为 mu(不是 mutex)或带后缀如 dataMu,这是 Go 社区约定,一眼能识别保护目标
  • 不要在 Struct 初始化时显式赋值 mu: sync.RWMutex{}——零值已足够,显式初始化反而容易误触发拷贝检测
  • 切忌把锁作为函数参数传递:func process(m sync.RWMutex) 是危险的,应传指针 *sync.RWMutex

真正难的从来不是选哪个锁,而是想清楚「哪段代码必须原子执行」「哪些访问可以并行」「中间状态是否对外可见」。锁只是工具,逻辑边界划错了,换什么锁都救不了。

text=ZqhQzanResources