atomic.LoadInt32比x++安全是因为前者是单条不可中断CPU指令,后者是读-改-写三步非原子操作;但atomic仅保单变量单操作原子性,不保逻辑一致性、不支持跨字段/跨步骤操作,且要求内存对齐,故不能替代mutex或channel。

Go 的 sync/atomic 包不是“更轻量的锁替代品”,而是**只适用于特定场景的底层原语**——它不能保护任意代码段,也不能替代 sync.Mutex 或 sync.RWMutex;用错地方反而会掩盖竞态、引发静默错误。
为什么 atomic.LoadInt32(&x) 比 x++ 安全,但不能直接替换所有读写?
因为 x++ 是三步操作:读取 x、加 1、写回 x,中间可能被其他 goroutine 插入修改;而 atomic.LoadInt32 和 atomic.AddInt32 是单条 CPU 指令完成,不可中断。
但注意:
-
atomic函数只对参数地址指向的单个变量生效,不保证其周围逻辑原子性(比如“先检查再更新”这种两步逻辑,必须用CompareAndSwap或加锁) - 所有
atomic操作要求变量地址对齐(int64在 32 位系统上需 8 字节对齐),否则在某些平台 panic(如 ARM、32 位 Windows) - 不能对结构体字段直接原子操作,必须把该字段单独声明为顶层变量,或用
atomic.Value封装整个结构体
int64 和 uint64 在 32 位系统上必须用 atomic,否则可能撕裂
在 32 位架构(如 i386、ARMv7)上,int64 写入不是原子的:CPU 需两次 32 位写入,若中途被抢占,其他 goroutine 可能读到高低位不一致的“撕裂值”(如高位是旧值、低位是新值)。
立即学习“go语言免费学习笔记(深入)”;
所以:
- 只要涉及
int64/uint64的并发读写,无论是否自增,都必须用atomic.LoadInt64、atomic.StoreInt64等 - 不要试图用
sync.Mutex保护int64字段来“省掉 atomic”——锁的开销远高于 atomic,且没解决对齐隐患 - Go 1.19+ 在 64 位系统上对
int64读写做了隐式对齐优化,但代码跨平台时仍应统一使用 atomic
CompareAndSwap 是唯一能做“条件更新”的原子操作
atomic.CompareAndSwapInt32 是实现无锁状态机、一次性初始化、幂等切换的核心。它的语义是:“仅当当前值等于预期旧值时,才设为新值,并返回是否成功”。
典型误用:
- 用
Load+Store模拟 CAS:竞态窗口存在,必然失败 - 在循环里不更新
old值:CAS 失败后一直拿旧快照重试,永远卡住 - 忽略返回值:失败却不重试,逻辑跳过
正确写法示例(懒加载单例):
var once int32
var instance *Config
func GetConfig() *Config {
if atomic.LoadInt32(&once) == 1 {
return instance
}
if atomic.CompareAndSwapInt32(&once, 0, 1) {
instance = newConfig()
}
return instance
}
atomic.Value 不是万能容器,有明确限制
atomic.Value 允许原子地读写任意类型(包括结构体、map、slice),但它要求:写入的值必须是可比较的(comparable),且同一地址不能混用不同底层类型(比如先 Store(int(42)),再 Store("hello") 会 panic)。
常见陷阱:
- 存入指针后,又存入相同类型的值(如
*T和T):类型不一致,panic - 存入 map/slice 后,继续在外部修改其内容:atomic 只保护“指针本身”,不保护底层数组或哈希表——这仍是数据竞争
- 误以为
Value能替代 channel 或 mutex:它只解决“一个值的发布”,不解决“多 goroutine 协作流程”
适合场景:配置热更新、全局只读对象发布、函数指针切换。
真正难的不是记住函数名,而是判断「这里到底该用 atomic、mutex 还是 channel」——atomic 只管单个变量的读写安全,一旦逻辑跨变量、跨步骤、需要等待或通知,就得换工具。



















