std::atomic必须显式初始化,否则值未定义;自定义类型需满足trivially copyable且对齐足够;load/store须指定memory_order,默认seq_cst性能差;atomic_flag是唯一强制lock-free的原子类型;指针算术需谨慎,避免隐式操作。

std::atomic 必须显式初始化,否则值未定义
很多刚用 std::atomic 的人会写 std::atomic<int> counter;</int> 就直接 ++,结果发现第一次读出来是随机大数。这不是 bug,是 C++ 标准规定:默认构造的 std::atomic 对象不保证初始化为零,它只做 trivial 构造,内存内容未被清零。
- 正确做法是显式初始化:
std::atomic<int> counter{0};</int>或std::atomic<int> counter = {0};</int> - 对于自定义类型(如
struct Point { int x,y; };),只有满足is_trivially_copyable_v<point></point>且对齐足够时才能用std::atomic<point></point>;否则编译失败或退化为锁实现 - 注意:
std::atomic<bool></bool>默认构造后值是false,这是唯一例外,但别依赖——显式写{false}更安全
load/store 不能省略 memory_order 参数,否则默认是 seq_cst
seq_cst(sequential consistency)最安全,但也最重。在高频计数、无依赖的标志位等场景下,它会强制刷新所有缓存行,拖慢性能。很多人以为“不写参数就没事”,其实是在默默付出代价。
- 读操作常用
.load(std::memory_order_acquire)(如检查运行标志) - 写操作常用
.store(true, std::memory_order_release)(如设置完成状态) - 自增/自减等读-改-写操作默认也是
seq_cst,高频循环里建议用.fetch_add(1, std::memory_order_relaxed)——前提是不依赖该操作与其他内存访问的顺序 - 混用不同 order 时,必须成对出现(acquire/release 配对,consume 已基本弃用)
std::atomic_flag 是唯一无锁且保证 lock-free 的原子类型
其他 std::atomic<t></t> 类型是否 lock-free 取决于平台和 T 的大小。比如 std::atomic<uint64_t></uint64_t> 在某些 32 位 ARM 上会回退到内部互斥锁,导致意外阻塞。而 std::atomic_flag 是标准强制要求 lock-free 的,适合做轻量级自旋锁。
- 它只有两个状态:
test_and_set()(置位并返回旧值)和clear()(清零),没有 load/store - 必须用
ATOMIC_FLAG_INIT初始化(C++20 起可直接用{}) - 别试图用它存业务数据——它不是容器,只是个“门闩”。真要自旋锁,优先考虑
std::mutex;只有极低延迟场景才手写atomic_flag自旋
不要对 std::atomic 指针做算术运算,除非你清楚它在做什么
std::atomic<int> p;</int> 支持 p += 2;,但这不是原子地加 2 个字节,而是加 2 * sizeof(int) 字节。更危险的是:它底层调用的是 fetch_add,而指针类型的 fetch_add 在部分平台(如旧版 GCC + ARM)可能不 lock-free,甚至触发陷阱指令。
立即学习“C++免费学习笔记(深入)”;
- 如果只是想原子更新指针值(如无锁栈头),用
.exchange()或.compare_exchange_weak()更明确、更可控 - 避免
++p这类隐式操作——可读性差,且容易误以为是“线程安全的遍历” - 指向动态对象的原子指针,务必确保对象生命周期长于所有可能的读取;
std::shared_ptr的原子操作(std::atomic_shared_ptr)在 C++20 才有,之前只能靠std::atomic<t></t>+ 手动管理
复杂点在于:原子操作的安全性不只取决于函数名,还绑定在内存序、类型尺寸、目标架构三者交集上。一个在 x86 上跑得飞快的 relaxed 计数器,在 ARM 或 RISC-V 上可能突然变慢,甚至行为不同。别只测逻辑,一定要在目标平台上验证 lock-free 属性(查 is_lock_free())。











