int64是原子计数器默认选择,因其在主流平台天然8字节对齐,可直映射cpu原子指令;混用普通赋值、忽略对齐或类型错误会导致静默错乱,load/store须成对使用,cas失败需显式处理,atomic.value仅保整体替换原子性。

直接用 atomic.AddInt64 就能安全实现并发计数器,但写错类型、混用普通赋值、忽略内存对齐,都会让计数器“看起来正常却实际错乱”。
为什么 int64 是原子计数器的默认选择
Go 的 atomic 包对 int32 和 int64 都有支持,但 32 位整数在 64 位系统上不保证天然对齐——而 int64 在所有主流平台(amd64、arm64)上都被编译器强制对齐,能直接映射到 CPU 的原子指令(如 x86 的 LOCK XADD)。int 更危险:它在 32 位系统是 4 字节,在 64 位系统是 8 字节,跨平台时行为不可控。
- ✅ 推荐声明:
var counter int64(包级变量或指针指向的变量) - ❌ 避免:
type Counter struct { Count int }然后对c.Count调用atomic.AddInt64—— 字段可能未对齐,运行时 panic 或静默失败 - ⚠️ 注意:局部变量逃逸到堆后,对齐可能失控;若必须局部使用,用
new(int64)分配并传指针
Load/Store 必须成对出现,否则读不到最新值
atomic.LoadInt64 不是“万能读”,它只对由 atomic.StoreInt64 写入的值保证可见性。CPU 缓存一致性依赖内存屏障,普通赋值(如 counter = 100)绕过屏障,其他 goroutine 可能永远看不到更新。
- 写操作必须用:
atomic.StoreInt64(&counter, 100) - 读操作对应用:
atomic.LoadInt64(&counter) - 禁止混用:
counter = 42+atomic.LoadInt64(&counter)→ 读端大概率拿到旧值 - 调试技巧:在读取前加
runtime.Gosched()无法修复这个问题,根源是写端没走原子路径
CompareAndSwapInt64 不返回错误,但失败必须处理
atomic.CompareAndSwapInt64 返回 bool,成功才为 true。它不 panic、不 log、不阻塞,失败就默默返回 false。很多初学者漏判返回值,导致状态切换逻辑被跳过或覆盖。
- 典型错误写法:
atomic.CompareAndSwapInt64(&state, 0, 1)后直接往下走 - 正确模式:
if !atomic.CompareAndSwapInt64(&state, 0, 1) { return errors.New("already initialized") } - 循环重试场景(如无锁栈 push)需配合
atomic.LoadInt64获取最新值再 CAS,否则陷入死循环 -
old参数不是“初始值”,而是你「此刻认为的当前值」;中间若有其他 goroutine 修改,它就失效了
atomic.Value 存结构体或 map 时,内部字段不安全
atomic.Value 只保证“整体替换”原子,不保护内部可变字段。存一个 map[string]int 后直接 m["k"] = v,会引发并发写 panic;存 struct{ Name string; Age int } 后改 s.Age = 30,也是非原子的。
- ✅ 安全用法:存不可变对象(如
struct{ Code int; Msg string }),或存指针(*Config),后续只读不改字段 - ❌ 危险用法:
var cfg atomic.Value; cfg.Store(map[string]int{"a": 1}); m := cfg.Load().(map[string]int; m["b"] = 2→ panic - 替代方案:需要频繁更新内部字段,老实用
sync.RWMutex;要高性能热更新配置,用atomic.Value存新指针,旧数据自然 GC - 注意:每次
Store都分配新对象,高频更新大结构体会加重 GC 压力
真正容易被忽略的不是函数怎么调,而是「哪些地方不该用 atomic」——比如涉及多字段联动的状态机、带条件分支的复合更新、需要回滚的事务逻辑。这些场景强行上 CAS,代码会越来越难懂,出 bug 的概率远高于性能收益。无锁不是目的,正确和可维护才是。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











