atomic.int64是go 1.19起首选方案,零值合法、自动处理内存对齐与内存序;嵌入struct时必须为首字段,所有操作须调用其方法,禁用混用旧式atomic函数。

直接用 atomic.Int64,别手写 int64 + atomic.AddInt64
Go 1.19 起,atomic.Int64 是首选方案——它零值合法、自动处理内存对齐和内存序,而裸 int64 配 atomic.AddInt64(&v, 1) 在 ARM 或 32 位环境极易因地址不对齐触发 SIGBUS panic。
-
var counter atomic.Int64即可声明,不用初始化,counter.Load()返回 0 - 嵌入 struct 时,必须是第一个字段;前面哪怕多一个
[1]byte,都可能破坏 8 字节对齐 - 所有读写必须走它的方法:
counter.Add(1)、counter.Load()、counter.Store(42),不能取地址传给旧式atomic.*Int64函数
需要「自增并返回旧值」?自己写 CAS 循环,别碰 Swap
atomic.Int64 没有 IncAndReturnOld() 方法,Swap() 也不适用——它不校验原值,会覆盖别人刚写入的新值,导致丢失更新。
- 正确写法是
Load+CompareAndSwap循环: func (c *atomic.Int64) IncAndGetOld() int64 { for { old := c.Load() if c.CompareAndSwap(old, old+1) { return old } } }- 高争用下可能重试多次,但比锁轻量;若业务能接受“返回新值”,直接
c.Add(1)最简 - 绝对不要写成
old := c.Load(); c.Store(old+1)——中间窗口期可能被其他 goroutine 修改
什么时候必须换 sync.Mutex?不是性能问题,是语义越界
atomic 只保单个变量的原子性。一旦逻辑跨步骤、跨字段或需条件判断,它就无能为力。
- 典型场景:计数器 + 最后更新时间戳 + 状态标志,三者必须一起更新,
atomic不支持批量操作 - 结构体字段类型不支持原子:比如
float64、map、自定义struct,只能用sync.Mutex或atomic.Value(后者要求类型固定) - 需要“如果当前值 > 100 就重置为 0”这类逻辑,
CompareAndSwapInt64能实现但易错难读;用Mutex封装更直观安全 - 性能不是换锁的理由:100 万次自增,
atomic.AddInt64比Mutex.Lock/Unlock快 5–10 倍;换锁是为表达清晰,不是妥协
最容易被忽略的两个坑:字段顺序和 API 混用
嵌入 atomic.Int64 时,字段顺序错误或混用新旧 API,不会编译报错,却可能让程序在某些平台启动即 panic,或者在高负载下间歇性失败。
- struct 中它必须是首字段;若前面有
[3]byte或string字段,地址偏移很可能非 8 字节对齐 - 一旦用了
atomic.Int64,所有访问必须走其方法;混用&counter传给atomic.LoadInt64会导致未定义行为 - 测试时加
-race未必能捕获这些问题——它们属于平台相关未定义行为,不是数据竞争
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











