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

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)和指针的底层原子操作,如 AddInt64、LoadUint64、StorePointer、CompareAndSwap 等。它不涉及 OS 级锁,开销极小,适合高频、简单、无分支逻辑的并发更新。
适用典型场景:
- 计数器(如请求总数、活跃连接数);
- 状态标志位(如
running int32,用atomic.LoadInt32判断是否运行中); - 无锁单链表、Ring Buffer 的游标推进;
- CAS 实现简易自旋锁或无锁栈(需谨慎,易出错)。
限制也很明确:不支持结构体、map、slice 等复合类型;无法组合多个原子操作成“事务”;错误使用(如忘记内存序)可能导致不可预测行为。
怎么选?看数据访问模式和操作复杂度
判断依据很简单:
- 只做简单数值增减/标志读写 → 优先 atomic;
- 读操作频繁、写极少,且临界区纯读 → RWMutex 更合适;
- 读写混合、逻辑稍复杂(如先查后改、多字段联动更新)、或需条件等待(配合 cond)→ Mutex 更稳妥;
- 不确定?从 Mutex 开始,用
-race测试 + pprof 观察争用,再针对性优化。
没有银弹。原子操作快但表达力弱,RWMutex 读友好但写易阻塞,Mutex 通用但有调度开销。真正关键的是理解你保护的数据“怎么被访问”,而不是追求某一种原语的炫技用法。










