Golang 并发同步原语:Mutex、RWMutex 与原子操作

2次阅读

go并发安全依赖mutex、rwmutex和atomic三类同步原语:mutex适用于通用读写互斥;rwmutex优化读多写少场景;atomic用于简单数值的无锁操作,选型需依数据访问模式而定。

Golang 并发同步原语:Mutex、RWMutex 与原子操作

Go 语言的并发安全不是靠“自觉”,而是靠明确的同步原语来保障。Mutex、RWMutex 和原子操作(sync/atomic)是三类最常用、最基础的工具,它们适用场景不同,性能和语义也有明显差异——选错可能引发死锁、数据竞争,或白白牺牲性能。

Mutex:最通用的互斥锁

sync.Mutex 是 Go 中最基础的排他锁,同一时间只允许一个 goroutine 进入临界区。它不区分读写,适合保护小段需要严格串行执行的逻辑,比如更新计数器、修改结构体字段、维护 map 的增删改(非并发安全 map)等。

使用要点:

  • 锁必须与被保护的数据在同一个作用域内定义,避免误传或误共享;
  • 务必成对调用 Lock()Unlock(),推荐用 defer mu.Unlock() 防止遗漏;
  • 不要复制已使用的 Mutex(Go 1.19+ 会 panic),应始终传递指针
  • Mutex 不可重入,同一个 goroutine 重复 Lock 会死锁。

RWMutex:读多写少时的性能优化选择

sync.RWMutex 提供读写分离能力:多个 goroutine 可同时读(RLock),但写(Lock)会独占,且阻塞所有新读请求。适用于“读远多于写”的场景,例如配置缓存、路由表、状态快照等。

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

关键细节:

  • 写锁优先级更高:一旦有 goroutine 请求写锁,后续的读请求会被排队,即使已有大量读持有者;
  • 读锁不能升级为写锁(即不能在 RLock 后再 Lock),否则死锁;
  • RLock / RUnlock 必须配对,且不能在未 RLock 的情况下 Unlock;
  • 注意“饥饿”问题:持续高频读可能导致写操作长期等待,必要时考虑降级为普通 Mutex 或引入超时机制。

原子操作:无锁、轻量、有限但高效

sync/atomic 提供对整数类型(int32/int64/uint32/uint64/uintptr)和指针的底层原子操作,如 AddInt64LoadUint64StorePointerCompareAndSwap 等。它不涉及 OS 级锁,开销极小,适合高频、简单、无分支逻辑的并发更新。

适用典型场景:

  • 计数器(如请求总数、活跃连接数);
  • 状态标志位(如 running int32,用 atomic.LoadInt32 判断是否运行中);
  • 无锁单链表、Ring Buffer 的游标推进;
  • CAS 实现简易自旋锁或无锁(需谨慎,易出错)。

限制也很明确:不支持结构体、map、slice 等复合类型;无法组合多个原子操作成“事务”;错误使用(如忘记内存序)可能导致不可预测行为。

怎么选?看数据访问模式和操作复杂度

判断依据很简单:

  • 只做简单数值增减/标志读写 → 优先 atomic;
  • 读操作频繁、写极少,且临界区纯读 → RWMutex 更合适;
  • 读写混合、逻辑稍复杂(如先查后改、多字段联动更新)、或需条件等待(配合 cond)→ Mutex 更稳妥;
  • 不确定?从 Mutex 开始,用 -race 测试 + pprof 观察争用,再针对性优化。

没有银弹。原子操作快但表达力弱,RWMutex 读友好但写易阻塞,Mutex 通用但有调度开销。真正关键的是理解你保护的数据“怎么被访问”,而不是追求某一种原语的炫技用法。

text=ZqhQzanResources