atomic.AddInt64比Mutex快得多,因其底层为单条CPU原子指令(如LOCK XADD),无调度、不抢锁、不自旋;而Mutex在高争用下会进入信号量等待,延迟飙升。

直接用 sync/atomic,别碰 sync.Mutex——除非你要做“加完判断是否超限”这类复合操作。纯计数本身是无状态、幂等的,锁带来的开销是原子操作的 10 倍以上。
为什么 atomic.AddInt64 比 Mutex 快得多
因为 atomic.AddInt64 底层调用的是 CPU 的 LOCK XADD(x86)或 LDAXR/STLXR(ARM)指令,单条指令完成读-改-写,不触发 Goroutine 调度、不抢锁、不自旋。而 sync.Mutex 在高竞争下会进信号量等待,实测 100+ goroutine 并发递增时,延迟从 15ns 暴涨到 200ns+。
-
atomic是为单变量整数增减设计的,适用场景明确:只做Inc/Dec/Load/Store -
sync.Mutex是为保护临界区设计的,哪怕只包一个counter++,也付出了调度和上下文切换的代价 - Go 1.19+ 推荐直接用
sync.AtomicInt64,语义更清晰;1.18 及之前必须手动封装atomic.AddInt64和atomic.LoadInt64
atomic 计数器必须满足的三个硬约束
违反任一条件都会导致 panic、数据错乱或未定义行为。
- 变量必须是全局或堆上分配的
int64(不能是栈上局部变量)——因为atomic需要稳定地址,栈变量地址不可靠 - 必须自然对齐:在 struct 中若前面字段总长不是 8 字节倍数,
atomic.AddInt64可能 panic;建议单独声明,或用//go:align 8注释(Go 1.21+) - 所有读写必须走
atomic函数:禁止混用counter++或counter = 100,否则竞态立即出现
什么时候非得换 Mutex 不可
当逻辑超出“单纯增减”范畴,比如需要条件判断、多字段协同、或配合非原子副作用时,sync.Mutex 反而更安全、更易维护。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- “仅当当前值 atomic.CompareAndSwapInt64 只做一次 CAS,失败后你得自己决定重试还是跳过
- 计数同时要更新时间戳或写日志:这些非原子动作必须包裹在同一把锁里,否则状态不一致
- 结构体中多个字段需同步变更(如
count和lastUpdated),atomic无法保证二者更新的原子性
高频场景下的分片优化(真瓶颈才考虑)
单个 atomic.AddInt64 在每秒千万级写入时,可能因 CPU 总线争用成为瓶颈——这不是常见情况,压测确认后再动。
- 按 goroutine ID 或哈希 key 分配到 N 个独立
int64变量(N 通常取runtime.NumCPU()) - 每个 goroutine 固定写其中一个分片,避免跨核缓存行失效(false sharing)
- 汇总时遍历所有分片求和;读取成本上升,但写入吞吐翻倍
- 注意:普通服务监控、QPS 统计完全不需要分片;只有压测发现
atomic.AddInt64占用 >10% CPU 时才值得引入
最容易被忽略的是:所有读取也必须用 atomic.LoadInt64,而不是直接取变量值——非原子读可能看到中间态,尤其在 ARM 等弱内存模型平台上。


















