必须用int64而非int,因atomic.AddInt64要求8字节对齐且跨平台一致,而int在32位系统为4字节、64位系统为8字节,行为不可控;计数器须为包级int64变量,读写必须配对使用atomic.LoadInt64和atomic.AddInt64,禁止裸读裸写或使用局部变量。

直接用 atomic.AddInt64 和 atomic.LoadInt64,计数器必须声明为 int64 类型的包级变量,不能是 int,也不能是局部变量。
为什么必须用 int64 而不是 int
Go 的 atomic 包没有 atomic.AddInt 函数——它根本不存在。底层原子指令(如 x86 的 LOCK XADD)要求固定字长对齐:int 在 32 位系统上是 4 字节,在 64 位系统上是 8 字节,行为不可控;而 int64 始终是 8 字节,所有平台一致,CPU 可原生支持。
- 声明写
var reqCount int64,不是var reqCount int,否则编译失败或 ARM 平台 panic - 初始化可写
0(Go 自动推导),但显式写int64(0)更安全 -
int32也可用,但仅限结构体字段对齐等明确控制场景;普通计数器无脑选int64
atomic.AddInt64 的正确调用方式
必须传入指针,且变量地址必须有效。局部变量取地址会逃逸或被编译器拒绝;栈上变量函数返回后地址失效,Go 直接报错:cannot take the address of。
- ✅ 正确:包级变量
var counter int64,然后在 handler 中调用atomic.AddInt64(&counter, 1) - ❌ 错误:函数内写
count := int64(0); atomic.AddInt64(&count, 1)(编译失败) - ⚠️ 结构体中放
int64字段时,避免前面紧挨着byte或bool,否则 ARM 上可能触发SIGBUS;Go 1.17+ 推荐用_ [0]uint64强制对齐
读取必须用 atomic.LoadInt64,不能直接读变量
直接读 counter 看似方便,但跳过内存屏障,多核 CPU 上极易读到过期缓存值。goroutine A 刚加了 1,goroutine B 立刻读,可能仍看到旧值。
立即学习“go语言免费学习笔记(深入)”;
- ✅ 安全读取:
current := atomic.LoadInt64(&counter) - ❌ 危险读取:
current := counter(竞态 + 缓存不一致) - 如果只是暴露给 Prometheus,每秒读一次足够;每毫秒轮询反而增加总线争用,得不偿失
- 需要“读+清零”?用
atomic.SwapInt64(&counter, 0),它返回旧值并设新值
什么时候该放弃原子操作,改用 sync.Mutex
原子操作只适合单指令完成的简单整数运算。一旦逻辑变复杂,比如“加 1 但不超过阈值”“先读再条件更新”,硬套 atomic 很容易写出竞态或死循环。
- ❌ 错误示范:
if atomic.LoadInt64(&counter) —— 中间存在竞态窗口,可能超限 - ✅ 正确做法:用
sync.Mutex包裹整个判断+更新逻辑 -
atomic.CompareAndSwapInt64虽能实现 CAS,但失败后需手动决定重试或放弃;写错容易陷入死循环或漏更新
真正容易被忽略的是:原子操作不是“无锁就一定快”。它省掉锁开销,但也牺牲了表达力;当业务逻辑开始包含状态依赖、条件分支或复合判断时,sync.Mutex 往往更清晰、更可靠。


















