
直接用 sync.Mutex 或 sync/atomic 就能保护计数器,但选哪个、怎么写、哪里容易崩,得看场景和数据类型。
sync.Mutex 保护 int 计数器的典型写法
适用于需要复合操作(比如先读再判再改)、或计数器是结构体字段、或要和其他逻辑共用同一把锁的场景。核心是锁住整个临界区,确保原子性。
-
Lock()必须在所有读/写共享变量前调用,Unlock()必须在所有操作后调用,且推荐用defer避免遗漏 - 不要在锁内做耗时操作(如 HTTP 请求、文件 I/O),否则会严重拖慢其他 goroutine
- 如果只对单个
int64做增减,sync.Mutex是“重了”,sync/atomic更轻量、无锁、性能更好 - 示例中若把
counter++拆成tmp := counter; counter = tmp + 1又没加锁,就立刻退化成竞态代码
sync/atomic.AddInt64 替代 mutex 的条件
只有当你的计数器是独立的 int64(或 int32、uint64 等原子支持类型),且只需要做加减、比较并交换(CAS)、加载、存储这类基础操作时,sync/atomic 才适用。
- 必须声明为
int64:用int在 32 位系统上不是原子的,atomic.AddInt64(&counter, 1)会 panic - 不能替代“读-改-写”逻辑:比如 “如果 count atomic.CompareAndSwapInt64 循环重试,不是简单一行能搞定
- 不提供内存屏障语义以外的同步:它不阻塞 goroutine,也不协调其他状态,别指望靠它实现锁语义
- 性能差异明显:100 万次递增,
atomic通常比Mutex快 3–5 倍,尤其在高并发下
sync.RWMutex 在计数器场景基本没用
读写锁只在「读远多于写」且「读操作本身开销大」时才有意义。计数器的 Value() 几乎就是一次内存读,用 RWMutex.RLock() 反而增加调度开销,还可能因写饥饿导致延迟毛刺。
立即学习“go语言免费学习笔记(深入)”;
- 如果你的“读”是带计算的(比如聚合多个 key 的 sum),那应该重构逻辑,而不是套 RWMutex
- 真正适合
RWMutex的是缓存 map、配置快照这类读频次极高、结构较重的数据 - 对纯计数器用
RWMutex,等于给自行车装涡轮增压——有,但没必要,还更难维护
sync.Map 不该用来做计数器
sync.Map 是为「读多写少 + key 动态分散」设计的,内部用分段锁+原子指针替换,对高频更新单个 key 的计数器反而更慢、GC 压力更大。
- 它的
LoadOrStore和Range接口语义复杂,不适合简单计数逻辑 - 如果你真需要按 key 计数(如统计 API 调用次数),优先用
map[string]int64 + sync.RWMutex(读多)或map[string]*int64 + sync.Mutex(写多) - 用
sync.Map存单个计数器值,就像用集装箱运一颗螺丝钉——能装,但浪费空间和力气
最容易被忽略的是:计数器是否真的需要全局可见?很多场景其实可以用局部累加 + 最终汇总(如每个 goroutine 自己算,最后用 atomic 合并),既避免锁争用,又减少内存同步开销。

















