必须用 atomic.addint64(&counter, 1) 而非 counter++ 或 load+store,因后者并发丢值;&counter 是因函数需内存地址操作且变量须可寻址、对齐;go 1.19+ 推荐 atomic.int64 以提升安全与可读性。

直接用 atomic.AddInt64,别写 counter++ 或手动 Load+Store —— 后两者在并发下必然丢值、结果不可靠。
为什么 atomic.AddInt64 必须传 &counter?
函数签名是 func AddInt64(ptr *int64, delta int64) int64,它要直接操作内存地址,不是拷贝值。传值会编译报错:cannot use counter (type int64) as type *int64。
更关键的是:变量必须可寻址,且地址生命周期足够长:
- ❌
atomic.AddInt64(&getStruct().count, 1)—— 函数返回的 struct 字面量是临时值,字段不可取址 - ❌
atomic.AddInt64(&m["k"], 1)—— map 值不可寻址 - ❌
atomic.AddInt64(&42, 1)—— 字面量不可取址,编译失败 - ✅
var counter int64; atomic.AddInt64(&counter, 1)—— 局部变量一般可行(但注意对齐) - ✅
var Counter int64 // 包级变量;atomic.AddInt64(&Counter, 1)—— 推荐,天然对齐、地址稳定
int64 在 32 位平台 panic 的真实原因
atomic.LoadInt64、atomic.AddInt64 等 64 位操作要求变量地址 8 字节对齐。32 位平台(如 GOARCH=386 或 armv7)不保证栈上局部变量或 struct 字段天然满足该条件,运行时检测失败即 panic:runtime error: invalid memory address or nil pointer dereference(本质是未对齐访问触发 SIGBUS)。
常见危险结构体:
type Bad struct {
hits uint32
total int64 // 偏移为 4 → 未对齐!
}
解法:
- 把
int64放 struct 开头 - 前面加 padding:
_ [4]byte或_ [8]byte - Go 1.17+ 可用
//go:align 64注释(需配合go:build) - 验证方式:
unsafe.Offsetof(s.total) % 8 == 0必须为 true
别用 LoadInt64 + StoreInt64 模拟加法
这是最隐蔽的错误之一:看似“读出来、加 1、写回去”,但中间没有原子性保障。
atomic.LoadInt64 和 atomic.StoreInt64 各自是原子的,但组合起来不是。两个 goroutine 同时执行时,可能都读到 100,各自加 1 写回 101,最终只加了 1 次。
而 atomic.AddInt64(&counter, 1) 编译为单条 CPU 原子指令(如 x86 的 LOCK XADD),硬件保证整个“读-改-写”不可分割,还自带 acquire/release 内存序。
其他等价但错误的写法:
-
counter++—— 底层三步,必竞态 -
counter += 1—— 同上 -
atomic.StoreInt64(&counter, atomic.LoadInt64(&counter)+1)—— 两步分离,非原子
Go 1.19+ 推荐用 atomic.Int64
相比裸指针操作,atomic.Int64 更安全、可读性更好,且编译期能检查类型匹配:
var id atomic.Int64 id.Add(1) // 返回新值 id.Load() // 读当前值 id.CompareAndSwap(old, new)
它的底层仍是 int64 字段,但封装了对齐和方法调用约束,规避了手写 &x 时容易出的地址/生命周期问题。不过仍要注意:一旦开始用 atomic.Int64,就不要混用 atomic.AddInt64(&id.v, 1) —— 这绕过了封装,破坏一致性。
真正容易被忽略的点是:**所有读写必须全程统一走 atomic 路径**。哪怕只有一处用了 counter = 0 或 fmt.Println(counter),就会破坏内存可见性,race detector 会报错,但线上可能只是偶尔读到旧值,极难复现。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











