直接用++或=对共享变量赋值在并发下不安全,因cpu可能将读-改-写拆分为多步,导致竞态和结果丢失;需用sync/atomic的原子操作保证可见性、有序性与原子性。

为什么直接用 ++ 或 = 对共享变量赋值在并发下不安全
Go 中多个 goroutine 同时读写一个普通整型变量(比如 int64)时,可能产生竞态:CPU 可能将读-改-写拆成多步,中间被其他 goroutine 插入操作,导致结果丢失。即使变量是 64 位对齐,go run -race 仍会报 Data Race,因为非原子写无法保证可见性与有序性。
解决方法不是加 sync.Mutex(开销大),而是用 sync/atomic 提供的底层原子指令——它编译为单条 CPU 原子指令(如 XADD、LOCK XCHG),天然无锁且高效。
- 仅支持固定类型:
int32、int64、uint32、uint64、uintptr、unsafe.Pointer和部分布尔/指针操作 -
atomic.AddInt64(&x, 1)比x++多一次内存屏障,确保其他 goroutine 能立即看到更新 - 所有函数都要求变量地址必须是对齐的(Go 全局变量和堆分配变量默认满足;栈上局部变量传地址给
atomic可能 panic)
atomic.LoadUint64 和 atomic.StoreUint64 的典型使用场景
适用于“只读配置”或“状态标志位”的并发读写,比如服务是否已关闭、当前最大请求 ID、开关标记等。这类场景不需要加减,只需安全地读取或覆盖。
常见错误是把普通变量直接传值进去:
立即学习“go语言免费学习笔记(深入)”;
var flag uint64 = 0 atomic.StoreUint64(flag, 1) // ❌ 编译失败:期望 *uint64,给了 uint64 atomic.StoreUint64(&flag, 1) // ✅ 正确
- 读操作
atomic.LoadUint64(&x)比直接x多一次 acquire barrier,防止编译器/CPU 重排到临界区之前 - 写操作
atomic.StoreUint64(&x, v)加 release barrier,确保前面的内存写在该写之前完成 - 不要用它们替代 struct 字段的原子访问——字段需单独声明为原子类型,或用
atomic.Value封装整个结构体
如何用 atomic.CompareAndSwapInt32 实现无锁计数器或一次性初始化
CompareAndSwap(CAS)是构建更复杂原子逻辑的基础:它只在当前值等于预期旧值时才写入新值,并返回是否成功。常用于实现自旋锁、懒加载单例、幂等状态切换。
例如实现一个只初始化一次的配置缓存:
var config atomic.Value
var initialized int32
func GetConfig() *Config {
if atomic.LoadInt32(&initialized) == 1 {
return config.Load().(*Config)
}
// 双检锁 + CAS 避免重复初始化
if atomic.CompareAndSwapInt32(&initialized, 0, 1) {
cfg := loadFromDisk()
config.Store(cfg)
}
return config.Load().(*Config)
}
- CAS 必须配合循环(自旋)才能实现等待逻辑,但 Go 标准库中多数封装已帮你做了(如
sync.Once底层就用 CAS) - 注意避免 ABA 问题:虽然 Go 中指针和整型 CAS 很少遇到,但如果涉及链表节点复用,需额外版本号字段
-
atomic.SwapInt32是无条件交换,比 CAS 少一次比较,适合“获取并重置”类操作(如清空统计计数器)
atomic.Value 为什么不能存 interface{} 以外的类型
atomic.Value 是唯一能安全存储任意类型值的原子容器,但它内部用 interface{} 包装,因此有隐含约束:存入的值必须是可寻址的(即不能是字面量或短生命周期临时值),且类型需一致。
典型误用:
v := atomic.Value{}
v.Store(42) // ✅ int 是合法类型
v.Store(int64(42)) // ✅ 显式类型也行
v.Store(42.0) // ✅ float64 也可以
v.Store(struct{}{}) // ✅ 空结构体也支持
// 但下面这行会在 Load 时 panic:
val := v.Load().(int) // ❌ 如果之前 Store 的是 int64,类型断言失败
- 每次
Store后,后续所有Load返回的都是同一具体类型(Go 运行时会检查) - 性能略低于原生原子类型(有 interface{} 动态调度开销),只在需要存结构体、切片、map 等复合类型时才用
- 不可用于存放包含 mutex、channel、function 等不可拷贝或含运行时状态的值(会导致 panic 或未定义行为)
atomic.AddInt64 安全地增计数没问题,但若业务要求“总数不超过 100”,仅靠原子加法无法做条件限制——这时候得回到 sync.Mutex 或更高级的并发原语。










