std::atomic 初始化必须显式,如 std::atomic<int> counter{0};默认构造不初始化底层存储,导致未定义行为;自定义类型需满足 trivially copyable 且无用户构造函数;memory_order 应按需选择,避免滥用 seq_cst;compare_exchange_weak 需配合循环使用;atomic_ref 要求严格对齐与生命周期管理。

atomic 初始化必须显式,不能依赖默认构造
很多刚用 std::atomic 的人会写 std::atomic<int> counter;</int> 然后直接 counter++,结果运行时崩溃或值随机——因为 std::atomic 的默认构造不初始化底层存储,内存是未定义的。
- 必须显式初始化:
std::atomic<int> counter{0};</int>或std::atomic<int> counter = ATOMIC_VAR_INIT(0);</int>(C++17 起已弃用后者,优先用花括号初始化) - 对自定义类型(如
struct Point { int x,y; };),只有满足 trivially copyable 且无用户定义构造函数,才能用std::atomic<point></point>,否则编译失败 - 注意:
std::atomic<bool></bool>是特例,它的默认构造会初始化为false,但依然建议显式写{false}保持风格统一
load/store 用 relaxed 还是 seq_cst?看是否需要顺序保证
默认的 load() 和 store() 是 std::memory_order_seq_cst,性能最差但行为最符合直觉;实际中多数场景不需要这么强的顺序约束,乱用会拖慢多核性能。
- 计数器累加(如引用计数):用
relaxed就够了,只要不依赖其他内存访问顺序 - 标志位通知(如
ready_flag.store(true, std::memory_order_release)配合ready_flag.load(std::memory_order_acquire)):必须成对使用 release/acquire,否则线程可能看到“部分更新”的状态 - 绝对避免在同一个
std::atomic上混用不同 memory order 的操作——比如一个线程用seq_cst,另一个用relaxed,语义上没问题,但调试和推理成本陡增,容易漏掉同步点
compare_exchange_weak 和 strong 的区别不只是“失败概率”
compare_exchange_weak 可能伪失败(spurious failure),即值没变也返回 false;strong 保证只在值真不等时失败。但关键不是“哪个更可靠”,而是循环写法是否健壮。
- 几乎总该用
weak+ 循环:它在 x86 上通常编译为单条cmpxchg指令,而strong在某些平台(如 ARM)可能隐含重试逻辑,反而更重 - 正确写法是:
int expected = counter.load(); while (!counter.compare_exchange_weak(expected, expected + 1)) { // expected 已被更新为当前值,继续重试 } - 如果循环体里有复杂计算或可能阻塞的操作,才考虑
strong避免反复执行副作用——但这种情况本身就该重构,别把业务逻辑塞进 CAS 循环
atomic_ref 是 C++20 新特性,别在旧项目里硬套
有人看到 std::atomic_ref 能对栈/堆变量做原子操作,就想用来“给已有结构体字段加线程安全”,结果编译不过或运行出错。
立即学习“C++免费学习笔记(深入)”;
- 它要求所引用对象的地址必须满足对齐要求(通常是
alignof(T)),普通int a;在栈上不一定满足;用alignas显式对齐才能安全使用 - 生命周期必须严格长于
atomic_ref实例——不能对临时变量、局部数组元素或即将析构的对象取atomic_ref - MSVC 2019 16.10+、GCC 11、Clang 12 才开始支持;C++17 项目强行启用会触发未定义行为,不是报错那么简单
load() 或 fetch_add(),而是判断哪条内存访问需要同步、哪些顺序约束可以省、以及当多个 atomic 变量共同参与协议时,如何避免 ABA 或丢失更新——这些没法靠查文档解决,得看具体数据流。










