atomic.Store*比普通赋值安全,因其编译为单条CPU原子指令并隐含release内存屏障,确保写入原子性及之前所有内存操作对其他goroutine可见,且必须传地址、变量需自然对齐、类型须明确指定。

sync/atomic 是唯一能直接保证基础类型内存写操作原子性的标准方案,但仅限于特定类型和单步操作;它不解决复合逻辑、结构体字段或跨变量依赖的原子性问题。
atomic.Store* 写入为什么比普通赋值安全
普通 x = 1 在多 goroutine 下可能被拆成“读旧值→改寄存器→写回”三步,中间被抢占就会丢失更新;而 atomic.StoreInt64(&x, 1) 编译为单条 CPU 指令(如 x86 的 MOV + LOCK 前缀),硬件级不可中断。
关键点不止是“不中断”,还包含内存屏障:Store 隐含 release 语义,确保它前面所有内存写操作对其他 goroutine 可见——比如你先更新 data 字段再 atomic.StoreInt32(&ready, 1),其他 goroutine 用 atomic.LoadInt32(&ready) 看到 1 时,一定能读到完整的 data。
- 必须传地址:
atomic.StoreInt64(x, 1)❌ 编译失败;atomic.StoreInt64(&x, 1)✅ - 变量需自然对齐:全局变量或堆分配变量默认满足;栈上局部变量取地址传给
atomic可能 panic -
int和uint不支持——平台相关,必须显式用int32或int64 - 浮点数要先转:
atomic.StoreUint64(&u64, math.Float64bits(f))
为什么 atomic.Load* 读取不能替代锁来保护多步逻辑
atomic.LoadInt64(&x) 能读到最新值,但它只管自己这一读。如果你写的是 “if atomic.LoadInt64(&x) > 0 { atomic.AddInt64(&x, -1) }”,这仍是竞态:两次原子操作之间存在时间窗口,其他 goroutine 可能已改过 x。
立即学习“go语言免费学习笔记(深入)”;
这种“读-判-写”组合不是原子的,atomic 不提供事务能力。正确做法是:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 用
atomic.CompareAndSwapInt64(&x, old, old-1)自旋重试,直到成功 - 或者直接上
sync.Mutex,尤其当逻辑超过两步、涉及多个变量或需阻塞等待时 - 别幻想“我只读不写就不用同步”——只要读的结果会影响后续行为(比如决定是否启动某个流程),就必须和写端有明确的同步契约
atomic.Value 用于结构体/切片/Map 的原子替换
当你需要原子地替换整个配置对象、缓存 map 或自定义结构体指针,atomic.Value 是标准解法。它内部用读写锁+指针原子交换实现,对外表现为“一次 Store 就全部生效,一次 Load 就拿到完整快照”。
注意两个硬限制:
- 首次
Store后,后续只能Store同一类型(比如存了*Config,就不能再存map[string]int),否则 panic - 它不帮你深拷贝;如果存的是可变对象(如
map),多个 goroutine 仍可能并发修改其内容——应存不可变结构或封装访问方法 - 不要用
atomic.Value存接口值再期待类型断言安全;类型一致性由使用者保障
典型用法:var config atomic.Value; config.Store(&Config{Timeout: 30}); cfg := config.Load().(*Config)
容易被忽略的内存序陷阱
atomic.LoadUint64 和 atomic.StoreUint64 默认是 relaxed 内存序:它们保证单次读/写原子,但不约束与其他内存访问的顺序。这意味着:
- goroutine A 执行
data = "hello"; atomic.StoreUint64(&flag, 1),B 执行v := atomic.LoadUint64(&flag); if v == 1 { print(data) }—— B 可能打印空字符串,因为编译器或 CPU 把print(data)提前到了 load 之前 - 真正起同步作用的是
atomic.AddUint64(&counter, 0)或atomic.CompareAndSwapUint64,它们带 acquire/release 语义 - 除非你明确在做性能敏感的无锁算法,否则优先用
atomic.Add*或CAS做同步点,而不是裸用Load/Store
最稳妥的做法,是把状态标志和关联数据打包进一个结构体,用 atomic.Value 整体替换——既免去内存序推理,又避免字段级错配。

















