std::atomic初始化必须显式,因默认构造不保证值为0且可能引发未定义行为;内存序需按语义选择,relaxed适用于无依赖场景,acquire-release用于跨线程同步;compare_exchange_weak允许伪失败,适合循环重试;fence仅约束非原子访问重排;atomic不保证复合操作线程安全。

std::atomic 初始化必须显式,不能靠默认构造隐式初始化
很多刚用 std::atomic 的人会写 std::atomic<int> counter;</int> 然后直接 counter++,结果运行时崩在构造函数里——因为 std::atomic 的默认构造不保证内部值为 0,且某些平台(如 ARM)上未初始化的 std::atomic 对象可能触发未定义行为。
- 必须显式初始化:
std::atomic<int> counter{0};</int>或std::atomic<int> counter = ATOMIC_VAR_INIT(0);</int>(C++17 起推荐前者) - 类成员变量也要在构造函数初始化列表中赋初值,不能在构造函数体内赋值(那只是普通赋值,不触发原子初始化语义)
-
std::atomic<bool></bool>默认构造是安全的(C++11 起保证为 false),但为了一致性和可读性,仍建议显式写{false}
load/store 用 memory_order_relaxed 就够了?得看场景
很多人看到性能优化就无脑加 memory_order_relaxed,结果在多核下出现“值更新了但别的线程看不到”的诡异现象。这不是 bug,是内存序没对齐业务语义。
- 计数器累加(如引用计数)、单纯信号量(如“任务完成”标志)、纯统计用途(如请求总数)——
load/store用memory_order_relaxed安全且高效 - 需要同步状态变化的场景(比如生产者写数据后设标志位,消费者等标志再读数据)——至少要用
memory_order_acquire(load) +memory_order_release(store)配对 - 跨线程依赖顺序(A 线程改完 x 再改 y,B 线程看到 y 就必须看到 x 的新值)——不能只靠 relaxed,得用 acquire-release 或更强的
memory_order_seq_cst
compare_exchange_weak 和 compare_exchange_strong 别乱换
compare_exchange_weak 在某些平台(尤其是 ARM、PowerPC)可能伪失败(spurious failure):值没变也返回 false。新手常以为是 bug,其实这是硬件限制导致的正常行为。
- 循环重试场景(如无锁栈 push/pop)——优先用
compare_exchange_weak,它在 x86 上和 strong 性能一致,在弱一致性架构上更轻量 - 单次尝试、不想写循环(比如实现一个“仅设置一次”的 flag)——用
compare_exchange_strong,避免逻辑绕弯 - 别把 weak 当“弱版本”,它不是“功能缩水”,而是“允许伪失败”;strong 是“保证不伪失败”,但代价可能是多一条重试指令或锁总线
std::atomic_thread_fence 不是万能同步开关
有人以为加个 std::atomic_thread_fence(std::memory_order_acq_rel) 就能替代所有原子操作,结果发现编译不过或者行为错乱——fence 本身不作用于任何变量,它只约束**周边非原子内存访问**的重排。
立即学习“C++免费学习笔记(深入)”;
- fence 对
std::atomic操作本身无效,它管的是普通变量(比如int data;)的读写顺序 - 想同步两个原子变量间的依赖?直接用
atomic.load(memory_order_acquire)比 fence 更清晰、更安全 - 真正需要 fence 的典型场景:非原子变量 + 原子标志配合(例如先写
data = 42;,再flag.store(true, memory_order_relaxed),这时前面的写必须对其他线程可见,就得在 store 前插atomic_thread_fence(memory_order_release))
最常被忽略的一点:std::atomic 不等于线程安全。它只保证单个操作的原子性,不保证复合操作(比如 ++counter 是 read-modify-write,本身原子;但 if (counter > 0) do_something(); 这种判断+动作就是竞态点)。这类逻辑得靠更高层设计,不是加个 atomic 就万事大吉。











