应优先使用 atomic.AddInt64 而非 sync.Mutex 的典型场景是全局请求计数器、指标埋点累加、连接数统计等只写不读或读少写多的单调增减操作;若需先读再写(如限流判断),则必须用 mutex。

Go 的 atomic 包能让你绕过 mutex 直接做线程安全的整数操作,但不是所有计数场景都适合——只有当操作是纯整数增减、无条件判断、不依赖中间状态时,atomic 才真正高效且正确。
什么时候该用 atomic.AddInt64 而不是 sync.Mutex
典型适用场景:全局请求计数器、指标埋点累加、连接数统计等「只写不读」或「读少写多」的单调递增/递减场景。一旦你需要「先读再决定是否写」(比如限流中的 if count atomic 就不够用了,得上 atomic.CompareAndSwapInt64 或退回到 mutex。
-
atomic.AddInt64是 CPU 级原子指令,无锁、无调度开销,单次调用通常在 10 ns 内 -
sync.Mutex在高争用下会触发 goroutine 阻塞和调度,延迟可能跳到 100 ns 以上甚至微秒级 - 注意:
atomic只保证单个操作原子性,不保证多个 atomic 操作之间的顺序——需要atomic.LoadInt64+atomic.StoreInt64配合 memory ordering(如atomic.LoadAcquire)才能控制可见性
atomic 计数器必须用指针传参,不能传值
这是最常踩的坑:atomic 函数签名全都是 func AddInt64(addr *int64, delta int64) int64,传入的是地址。如果你写 var n int64; atomic.AddInt64(n, 1),Go 编译器会直接报错 cannot use n (type int64) as type *int64。
- 正确写法是
atomic.AddInt64(&n, 1),取地址符号&不可省略 - 如果变量在 struct 里,确保字段对齐:int64 字段最好放在 struct 开头或前面补足 8 字节对齐,否则在 32 位系统或某些 GC 场景下可能触发非对齐 panic(虽然现代 Go 版本已缓解,但仍是隐患)
- 不要对局部变量取地址后传给其他 goroutine 长期使用——栈变量可能被回收;应分配在堆上(如全局变量、struct 字段、或显式
new(int64))
读取计数器时别忽略 memory ordering
单纯用 atomic.LoadInt64(&n) 是安全的,但它的语义是 relaxed load:不保证之前其他内存操作的完成顺序。如果你在读计数器前刚写过某个标志位,又希望读到最新值,就得用更强语义:
- 一般监控读取用
atomic.LoadInt64就够了 - 若用于同步逻辑(比如等待计数器达到某值才继续),要用
atomic.LoadAcquire(Go 1.19+)或搭配runtime.Gosched()避免忙等耗尽 CPU - 错误示范:
for atomic.LoadInt64(&done) == 0 { }—— 这是典型的忙等,且编译器可能优化掉重复读取;应改用sync.WaitGroup或chan struct{}做通知
真正难的从来不是“怎么加一”,而是想清楚这个“一”在并发上下文里代表什么含义、谁需要看到它、以及看到的时候它是否该被看到。atomic 让操作变快,但也让逻辑更脆——少一次 mutex 的保护,就多一分对执行序和业务语义的依赖。











