std::atomic 初始化必须显式,如 std::atomic counter{0}; 默认构造为未初始化状态;自定义类型需满足 is_trivially_copyable 等条件;load/store 的 memory_order 决定语义而非仅性能;fetch_add 等操作原子但不防溢出和 aba;atomic_ref 要求严格对齐与生命周期。

atomic 的初始化必须显式,不能依赖默认构造
很多初学者写 std::atomic<int> counter;</int> 后直接用 counter.load(),结果行为未定义——因为 std::atomic 的默认构造是“未初始化”状态,不保证内部值为 0。必须显式初始化:
-
std::atomic<int> counter{0};</int>(推荐,C++11 起支持) -
std::atomic<int> counter = ATOMIC_VAR_INIT(0);</int>(C++17 已弃用,仅兼容旧代码)
对自定义类型(如 struct Point { int x,y; };),只有满足 is_trivially_copyable 且无虚函数/非静态成员引用时,才能用 std::atomic<point></point>;否则编译失败,得改用 std::atomic_ref 或锁。
load/store 的 memory_order 参数不是可选的“性能开关”,而是语义约束
写 counter.load() 看似简洁,实则等价于 counter.load(std::memory_order_seq_cst),这是最严格也最慢的顺序。但如果你只是读一个计数器用于日志打印,完全可以用 counter.load(std::memory_order_relaxed) 来避免不必要的内存栅栏。
-
std::memory_order_relaxed:只保证原子性,不约束前后指令重排 → 适合计数、标志位 -
std::memory_order_acquire:后续读写不能被重排到该 load 之前 → 常用于读取同步标志后访问共享数据 -
std::memory_order_release:前面读写不能被重排到该 store 之后 → 常用于写完数据再置位完成标志
混用 acquire/release 是成对出现的,单边乱设会导致数据竞争——比如生产者用 relaxed 写标志,消费者用 acquire 读,就无法建立 happens-before 关系。
立即学习“C++免费学习笔记(深入)”;
fetch_add 等读-改-写操作天然线程安全,但要注意溢出和 ABA 问题
counter.fetch_add(1) 是原子的,返回旧值;counter++ 则不是(先 load 再 add 再 store,三步非原子)。但它不检查溢出:
- 对
std::atomic<uint32_t></uint32_t>执行fetch_add(1)到 UINT32_MAX 后会回绕,不会抛异常 - 若需饱和运算,得自己加锁或用 CAS 循环校验
- ABA 问题在指针场景更突出:比如链表头指针被 A→B→A 修改两次,CAS 误判为未变;此时应改用
std::atomic<:shared_ptr>></:shared_ptr>或带版本号的 tag-pointer 技巧
atomic_ref 是 C++20 引入的“零成本绑定”,但要求对象生命周期和对齐严格
当你有一块已存在的内存(比如全局数组、结构体字段),又不想把它改成 atomic<t></t> 类型时,std::atomic_ref 就派上用场了:
int arr[1024];
std::atomic_ref<int> ref{arr[5]}; // 绑定到 arr[5]
ref.store(42, std::memory_order_relaxed);
但必须满足两个硬性条件:
- 被绑定对象的地址必须满足
alignof(T)对齐(例如int通常需 4 字节对齐) - 对象生命周期必须长于
atomic_ref实例——不能绑定栈变量然后返回atomic_ref
不满足时行为未定义,且多数编译器不会报错,只在运行时崩溃或静默错误。
真正难的不是记住 memory_order 枚举值,而是判断哪段逻辑需要 acquire-release 配对、哪段可以放心用 relaxed;还有就是别把 atomic 当万能锁——它快,但表达能力有限,复杂同步还得靠 mutex 或 condition_variable。











