go的atomic函数必须传指针,因其需直接操作内存地址,传值会编译失败,传错类型或未对齐则运行时panic;cas须循环重试,仅保单操作原子性,不保证多操作间顺序。

Go 的 sync/atomic 包不是“类”,它是一组函数式原子操作工具,不能像 Java 的 AtomicInteger 那样实例化对象;直接用函数操作变量地址即可,但必须严格匹配类型和对齐要求,否则运行时 panic。
atomic.AddInt32 等函数为什么必须传指针?
因为原子操作需要直接作用于内存地址,atomic.AddInt32 修改的是底层内存位置的值,而非副本。传值会导致编译失败(类型不匹配),传错类型的指针(比如把 int64 地址传给 AddInt32)会在运行时触发非法内存访问 panic。
-
int32变量必须用&v传给AddInt32、LoadInt32等配套函数 -
int64必须用AddInt64,且在 32 位系统上要求变量地址 8 字节对齐(go vet会报atomicalignment warning) - 结构体字段若要原子访问,该字段需单独声明为支持类型(如
count int64),且不能嵌套在未对齐结构中
CompareAndSwapInt32 是无锁编程的唯一可靠入口
CAS 不是“高级技巧”,而是构建任何无锁逻辑(如状态机切换、懒初始化、无锁栈)的最小可行原语。它天然带重试循环语义,但很多人误以为一次调用就能成功。
-
CompareAndSwapInt32(&state, 0, 1)返回false说明当前值已变,必须重试——不写 for 循环就等于没用 CAS - 不要用 CAS 模拟读-改-写复合逻辑(例如“如果
val > 5就加 1”),这会引入竞态:判断和修改之间有时间窗口 - 注意 ABA 问题:值从 A→B→A,CAS 会误认为未变;简单计数器场景可忽略,但涉及指针重用(如无锁队列节点回收)时必须用带版本号的方案
atomic.Int64 等封装类型比裸函数更安全,但仅限 Go 1.19+
atomic.Int64 是类型安全的封装,内部自动处理对齐与方法绑定,避免手写 LoadInt64(&x) 时传错地址或类型。但它不是万能替代:
- 它无法用于已存在的全局变量(比如 legacy 代码里的
var counter int64),必须声明为var counter atomic.Int64 - 它的
Load()/Store()方法仍基于指针运算,底层仍是LoadInt64/StoreInt64,性能无差异 - 它不提供
CompareAndSwap的便捷方法(需调用CompareAndSwap字段),而裸函数有CompareAndSwapInt64
真正容易被忽略的点是:原子操作只保单个操作的不可分割性,绝不保证多个原子操作之间的顺序或可见性。比如连续调用 StoreInt32(&a, 1) 和 StoreInt32(&b, 2),其他 goroutine 可能观察到 b 更新了但 a 还是旧值——除非显式插入 atomic.StoreUint64(&seq, ...) 或使用 atomic.MemoryBarrier()(已废弃,应改用 runtime.Gosched() 或 channel 同步)。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











