std::atomic变量必须用load()/store()或原子操作符读写,不可直接赋值;初始化推荐{}语法;内存序按需选择relaxed/release-acquire/seq_cst;仅支持trivially copyable且尺寸适配的类型;compare_exchange_weak需循环重试。

atomic 的初始化和基本读写操作
直接用 std::atomic 声明变量后,不能像普通变量那样用 = 赋值或读取——它会编译失败。必须显式调用 load() 和 store(),或者用 operator++ 等重载接口(仅对整型、指针等特化类型可用)。
-
std::atomic是安全的零初始化;但counter{0}; std::atomic在某些旧编译器上可能触发隐式转换警告counter = 0; - 读取推荐用
counter.load(std::memory_order_relaxed),不加参数默认是seq_cst,开销大且常不必要 - 写入用
counter.store(42, std::memory_order_release),避免直接赋值counter = 42(虽能编译,但语义模糊,易误导) - 自增操作可直接写
++counter或counter.fetch_add(1),后者返回旧值,更利于无锁逻辑判断
memory_order 选哪个?别一上来就用 seq_cst
std::memory_order_seq_cst 是最严格、最慢的内存序,也是所有原子操作的默认值。但它在多数场景中属于“过度同步”——比如生产者只写、消费者只读的单向通信,release/acquire 就够了。
- 纯计数器累加(无依赖其他变量):用
relaxed即可,性能接近普通变量 - 标志位通知(如
ready_flag.store(true, release)配合ready_flag.load(acquire)):必须成对使用release/acquire,才能保证之前写的非原子数据对另一线程可见 - 需要全局顺序一致的多变量协同(如双链表插入+计数更新):才考虑
seq_cst,但要意识到它会强制刷新 store buffer,可能拖慢整个 core
哪些类型能塞进 atomic?别硬塞自定义结构体
std::atomic 只支持 平凡可复制(trivially copyable) 且 大小不超过平台最大原子宽度 的类型。x86-64 通常是 16 字节,但实际支持因编译器而异。
- 内置类型(
int、long long、指针)基本都行;std::shared_ptr有特化版本,可用 -
std::atomic<:string>编译失败——std::string非 trivial,且动态分配内存 - 自定义 struct 若含虚函数、非 trivial 构造/析构、或成员含 non-trivial 类型(如
std::vector),一律不可原子化 - 想原子更新多个字段?别打包 struct,改用
std::atomic手动位拆分,或用compare_exchange_weak循环 CAS
compare_exchange_weak 为什么总要写在 while 循环里?
compare_exchange_weak 可能因底层硬件原因“伪失败”(spuriously fail),即当前值确实等于 expected,但仍返回 false。这不是 bug,是 x86 外平台(如 ARM)的常见行为,所以必须循环重试。
立即学习“C++免费学习笔记(深入)”;
- 典型模式:
int expected = old_val; while (!counter.compare_exchange_weak(expected, new_val)) { // expected 已被自动更新为当前实际值 } - 别用
compare_exchange_strong图省事——它在某些平台会生成更重的指令(如带 barrier 的 LL/SC),性能反而差 - 注意:
expected是 in-out 参数,失败时会被覆写为当前值,这是循环能继续的关键 - 如果业务逻辑允许“尝试一次”,那就不用循环,但得接受失败可能性——比如乐观锁更新失败后降级为加锁重试
真正难的不是记住这些规则,而是判断某段代码里哪几个内存访问之间存在依赖、是否需要同步、以及同步到什么程度。很多看似“正确”的 atomic 用法,其实只是没暴露竞态——换台 CPU、换个编译器优化等级,就崩了。










