应使用 atomic.AddInt64 和 atomic.LoadInt64 对 int64 类型计数器进行无锁原子操作,避免数据竞争和缓存不一致;禁止直接读写、使用 int 类型或非原子操作。

为什么不能直接用普通 int 做请求计数
多个 goroutine 并发写同一个 int 变量时,会触发数据竞争(data race),Go runtime 在 race detector 模式下会报 fatal error: concurrent map writes 类似错误——虽然这里没 map,但只要是未同步的整数读写,就可能丢失更新。比如两个 goroutine 同时执行 counter++,底层是“读-改-写”三步,中间穿插会导致一次加法被覆盖。
用 sync.Mutex 能解决,但锁开销大,尤其在高 QPS 场景(如每秒数万请求)下,锁争用会让性能断崖下跌。atomic 提供无锁原子操作,是更轻量的选择。
atomic.AddInt64 是最常用且安全的计数方式
Go 的 atomic 包不支持对 int 直接原子操作(因平台字长差异),必须显式使用带位宽的类型:计数器一律声明为 int64,再用 atomic.AddInt64 递增。
- ✅ 正确写法:
var reqCount int64 // 在 handler 中: atomic.AddInt64(&reqCount, 1)
- ❌ 错误写法:
counter++、counter += 1、或用atomic.AddInt(该函数不存在) - ⚠️ 注意:变量必须是全局变量或包级变量,不能是栈上局部变量(取地址会报错)
- ⚠️ 初始化必须用
int64(0)或字面量0(Go 会自动推导),避免混用int和int64导致编译失败
读取计数器时要用 atomic.LoadInt64
直接读 reqCount 看似能拿到值,但不保证看到最新写入(缺乏内存屏障),尤其在多核 CPU 上可能读到过期缓存值。必须用 atomic.LoadInt64 保证可见性与顺序性。
- ✅ 安全读取:
current := atomic.LoadInt64(&reqCount) - ❌ 危险读取:
current := reqCount(竞态 + 缓存不一致风险) - ? 如果只是日志打点或 Prometheus 指标暴露,通常每秒读一次足够,不必频繁调用;高频读本身也会成为瓶颈,可考虑加简单采样或滑动窗口聚合
需要重置或条件更新?用 CompareAndSwapInt64
单纯计数用 AddInt64 和 LoadInt64 就够了。但如果要做“归零重计”或“仅当小于某阈值才增加”,就得用 atomic.CompareAndSwapInt64 实现 CAS 逻辑。
- ? 重置示例:
for { old := atomic.LoadInt64(&reqCount) if atomic.CompareAndSwapInt64(&reqCount, old, 0) { break } }(注意:这不是原子清零,而是“读-比-换”,需循环直到成功) - ⚠️ CAS 不是万能的:失败后要自己决定重试还是放弃,写错容易陷入死循环或漏更新
- ? 更复杂的统计(如分桶、滑动窗口)别硬刚 atomic,应切换到
sync.Pool+ 分片计数器,或直接用prometheus.Counter这类成熟封装
atomic 是利器,但只适合“单个数字的高频增减”。一旦逻辑变复杂,强行堆砌 CAS 很快会让代码难以验证和维护。











