std::atomic 初始化必须显式,默认构造为未初始化状态;正确方式为 std::atomic<T> var{0}; 或(C++20前)ATOMIC_VAR_INIT(0);未初始化读取将导致未定义行为。

atomic 初始化必须显式,不能依赖默认构造
很多初学者写 std::atomic<int> counter;</int> 就以为它初始值是 0,其实不是——std::atomic 的默认构造是“未初始化”(uninitialized),读取会触发未定义行为。真正安全的初始化只有两种方式:std::atomic<int> counter{0};</int> 或 std::atomic<int> counter = ATOMIC_VAR_INIT(0);</int>(后者 C++20 已弃用,但兼容旧代码)。C++17 起推荐统一用花括号初始化。
常见错误现象:counter.load() 返回随机大数、程序偶发崩溃、ASan 报告“uninitialized memory read”。
- 所有内置类型(
int、bool、void*)都支持std::atomic,但自定义类型必须满足 trivially copyable 且 sizeof -
std::atomic_flag是唯一保证无锁的类型,适合做自旋锁基元,但它不提供 load/store,只支持test_and_set()和clear() - 别用
std::atomic<t>::operator=</t>给非 trivial 类型赋值——它不保证原子性,底层调的是非原子 memcpy
memory_order 选错会导致看似正常却出错的并发行为
不写内存序参数时,默认是 std::memory_order_seq_cst,最安全也最慢;但实际中多数场景可以降级。比如计数器递增用 memory_order_relaxed 完全没问题,而发布指针(publish-pointer pattern)必须用 memory_order_release + memory_order_acquire 配对。
典型踩坑:用 relaxed 实现引用计数释放逻辑,结果对象被提前析构;或在生产者-消费者里用 relaxed 更新标志位,消费者永远看不到 true。
立即学习“C++免费学习笔记(深入)”;
-
relaxed:仅保证该操作自身原子,不约束前后指令重排 → 适合计数、统计等独立变量 -
acquire/release:构成同步点,类似锁的 acquire/release 语义 → 适合状态标志、指针发布 -
seq_cst:全局顺序一致,性能开销最大 → 仅当需要跨多个原子变量建立明确先后关系时才必要
compare_exchange_weak 和 compare_exchange_strong 的区别不只是“失败概率”
compare_exchange_weak 在某些平台(尤其是 ARM、LL/SC 架构)可能伪失败(spuriously fail),即当前值等于 expected 却返回 false;strong 版本则不会。但这不是“weak 更差”,而是 weak 更轻量——它允许编译器生成更紧凑的汇编,尤其在循环中配合 while 使用时性能更好。
使用场景几乎总是:用 weak 写循环 CAS,用 strong 做单次尝试(比如实现 try_lock)。
- 正确写法:
while (!counter.compare_exchange_weak(expected, desired)) { /* expected 自动更新为当前值 */ } - 别把
expected声明为 const —— 它会被函数内部修改 - 在 x86 上两者汇编几乎一样,但在 ARM 上
weak可能省掉一条 barrier 指令
无锁结构里不能直接 delete 动态对象
这是最隐蔽也最致命的问题:一个线程刚 delete 掉节点,另一个线程还在通过原子指针访问它,就产生 use-after-free。标准解法是延迟回收(deferred reclamation),比如用 hazard pointer 或 epoch-based reclamation(EBR)。C++20 的 std::atomic<:shared_ptr>></:shared_ptr> 也不能解决这个问题——它只保 shared_ptr 本身操作原子,不保所指对象生命周期。
容易被忽略的点:即使你用 std::atomic<t></t> 管理指针,只要涉及多线程读写同一块堆内存,就必须配套内存回收机制。
- 简单项目可先用
std::shared_ptr包裹节点,靠引用计数延后销毁,但注意:原子操作的是指针,不是对象内容 - 真正高性能无锁容器(如 queue、stack)必须引入 RCU 或 EBR,否则只是“无锁接口,有锁语义”
- Clang/GCC 的
-fsanitize=thread能抓到部分 use-after-free,但无法覆盖所有无锁场景











