必须用int64而非int,因atomic.AddInt64要求固定8字节对齐且跨平台一致,而int在32位系统为4字节、64位系统为8字节,行为不可控;所有读写必须用atomic函数配对,否则绕过内存屏障导致读到过期值。

直接用 atomic.AddInt64 和 atomic.LoadInt64,计数器必须声明为 int64 类型且是包级或全局变量;别用 counter++、别用 int、别读写混用非原子操作。
为什么必须用 int64 而不是 int
Go 的 atomic 包不提供 atomic.AddInt 函数——它根本不存在。底层原子指令依赖固定字长对齐,int 在 32 位系统上是 4 字节,在 64 位上是 8 字节,行为不可控。而 int64 始终是 8 字节,所有平台一致,且能被 CPU 原子指令(如 x86 的 LOCK XADD)原生支持。
-
int32也可用,但仅限明确控制内存布局的场景(如结构体字段对齐),普通计数器优先选int64 - 声明时写
var reqCount int64,不是var reqCount int,否则编译报错或运行时 panic(尤其在 ARM 平台) - 初始化可写
0(Go 自动推导为int64),但显式写int64(0)更安全
atomic.LoadInt64 不是可选项,是必须项
直接读 reqCount 看似方便,但会跳过内存屏障,多核 CPU 上极易读到过期缓存值。比如 goroutine A 刚用 atomic.AddInt64 加了 1,goroutine B 立刻读 reqCount,可能仍看到旧值。
- ✅ 正确读取:
current := atomic.LoadInt64(&reqCount) - ❌ 危险读取:
current := reqCount(竞态 + 缓存不一致) - 如果只是暴露给 Prometheus,每秒读一次足够;高频轮询(如每毫秒)反而增加总线争用,得不偿失
别把原子变量放在栈上或结构体里乱放
atomic 函数必须传变量地址,而局部变量地址在函数返回后失效,Go 编译器会直接拒绝:cannot take the address of ...。更隐蔽的问题是结构体内嵌 int64 字段若未对齐(比如前面紧挨着一个 byte),在 ARM 上可能触发 panic。
- ✅ 安全位置:包级变量(
var reqCount int64)、结构体中导出的顶层字段(type Stats struct { Total int64 }) - ❌ 危险位置:函数内
func() { count := int64(0); atomic.AddInt64(&count, 1) }(编译失败) - ⚠️ 结构体字段对齐建议:用
align64标签(Go 1.17+),或前置占位字段_ [0]uint64强制对齐
需要重置或条件更新?用 CompareAndSwapInt64,别手写锁
单纯累加用 AddInt64 就够。但要做“归零”或“超阈值禁止再增”,就得靠 CAS。它不是原子指令,而是“读-比-换”循环,必须自己兜底重试。
- 重置示例:
for { old := atomic.LoadInt64(&reqCount); if atomic.CompareAndSwapInt64(&reqCount, old, 0) { break } } - CAS 失败不等于错误,只是当前值已变,需重新读再试
- 避免写成
if reqCount ——这中间有竞态窗口,两个 goroutine 可能同时通过判断然后都加 1
最易被忽略的是:原子操作只保单条指令安全,不保逻辑组合。比如“加 1 后发告警”,加法是原子的,但加完再发消息这整段不是。这种复合动作该用 sync.Mutex 或 channel,硬套 atomic 只会埋下更深的坑。











