必须全程使用atomic接口操作计数器,混用普通读写必然导致竞态和更新丢失;推荐go 1.19+的atomic.int64类型,安全且高效;多维统计需分片锁或atomic.value;避免热路径中耗时操作。

直接用 atomic.AddInt64 或 atomic.AddUint64,但必须全程只走 atomic 接口,混用普通读写会立刻导致竞态——这不是“可能出错”,是必然丢失更新。
为什么 counter++ 在并发下一定不准
它被拆成三步:读当前值 → 加 1 → 写回。两个 goroutine 同时读到 42,各自加 1 后都写 43,一次自增就没了。Go 的 -race 检测器会立刻报 Data Race;不加检测也可能跑出 987/1000 这种随机结果。
- 必须用
int64或uint64类型(int在 32 位平台行为不一致) - 所有操作必须传地址:
&counter,传值会编译失败 - 读也要用
atomic.LoadInt64(&counter),不能直接fmt.Println(counter)
高频单指标计数:选 atomic.Int64 还是老式函数?
Go 1.19+ 推荐用类型安全的原子类型,比如 var counter atomic.Int64,它自带方法链、避免类型误用,且底层仍是同一套指令。
-
counter.Add(1)替代atomic.AddInt64(&c, 1) -
counter.Load()替代atomic.LoadInt64(&c) -
counter.CompareAndSwap(0, 1)更清晰,不用手动传地址 - 旧函数接口仍完全可用,但新类型能防住常见低级错误(如传错类型指针)
多个维度统计时,atomic 还够用吗?
不够。atomic 只保单个变量的原子性,一旦你要「按 HTTP 状态码 + 路由路径」分桶计数,就得换方案。
- 别用
sync.Map做高频写入采样:它为读多写少设计,写多时性能反不如map+sync.RWMutex - 固定 key 数量少(比如几十个状态码)→ 用分片锁(sharded map),按 hash 分 8–16 个子 map,每个配独立
sync.Mutex - key 动态增长或维度复杂 → 改用
atomic.Value存整个聚合结构体(但注意:每次更新需构造新结构体并Store,非增量) - 千万别在循环里对
sync.Map调用LoadOrStore做累加:它不是原子加法,竞态下丢数据
真正卡性能的往往不是 atomic,而是你后续干的事
atomic 本身极快,但高频调用后立刻 log.Printf、json.Marshal 或发 HTTP,会拖垮整个热路径。
- 每秒几千次计数 → 不要每次触发上报,改用
time.Ticker每 5 秒批量拉取counter.Load()并汇总 - 避免在计数热路径拼接字符串:
fmt.Sprintf("req_%s_%d", path, code)分配不可控,优先用预定义 key 或strings.Builder - 如果上报依赖外部服务,确保失败时计数不丢:本地先存,异步重试,别让网络延迟阻塞计数逻辑
最常被忽略的一点:只要用了 atomic,这个变量的所有访问——无论读、写、判断、交换——都必须走 atomic 函数。漏一处普通赋值,内存可见性和顺序保证就全失效,这不是慢一点的问题,是结果不可信。











