atomic 通过硬件原子指令和内存屏障解决数据竞争,适用于简单状态同步;但非万能锁替代品,多变量协同更新仍需 mutex。

atomic 是 C++ 中避免数据竞争最轻量、最直接的工具,但它不是万能锁替代品——用错场景反而引发更隐蔽的 bug。
atomic 为什么能解决数据竞争?
数据竞争的本质是多个线程对同一内存位置做**非原子的读-改-写**(如 i++),而 std::atomic 保证这类操作在硬件或编译器层面不可分割。它不靠互斥锁阻塞线程,而是依赖 CPU 提供的原子指令(如 x86 的 LOCK XADD)或内存屏障(memory barrier)来同步缓存与主存。
- 必须显式声明:原始类型(
int、bool等)不能直接当原子变量用,得包装成std::atomic - 默认使用
memory_order_seq_cst(顺序一致性),语义最强但可能有性能开销;高频场景可降级为relaxed或acquire/release - 注意:
std::atomic不保证对象内所有成员都原子——比如std::atomic<:string>是非法的,因为std::string不是 trivially copyable
哪些操作真正“原子”?哪些只是“看起来像”?
不是所有 std::atomic 成员函数都生成单条 CPU 指令。例如:
-
load()、store()、exchange()、compare_exchange_weak/strong()—— 真正原子,对应硬件指令 -
operator++()、fetch_add()—— 原子,但底层可能是LOCK ADD类指令 -
operator=赋值:仅对 trivial 类型(如int)是原子的;对自定义 struct,即使声明为std::atomic,也仅当MyStruct是 trivially copyable 且大小 ≤ 指针宽时才可用 lock-free 实现,否则退化为内部互斥锁(可通过is_lock_free()检查)
和 mutex 相比,什么时候该选 atomic?
atomic 适合简单共享状态同步,比如计数器、标志位、无锁队列节点指针更新;一旦逻辑涉及多个变量协同变更(如“先检查余额再扣款”),就必须用 std::mutex 或事务性内存(TSX)等更高阶机制。
立即学习“C++免费学习笔记(深入)”;
- ✅ 推荐用 atomic:
std::atomic作线程启动信号;ready{false}; std::atomic做统计计数counter{0}; - ❌ 别硬套 atomic:想原子地更新两个 int 字段(
x和y)?不能靠两个std::atomic,得用 mutex 包裹整个读-改-写块 - ⚠️ 容易踩坑:
compare_exchange_weak可能伪失败(spurious failure),必须放在循环里重试;compare_exchange_strong更可靠但可能稍慢
一个典型错误:以为 atomic 就不用考虑内存序
在弱一致性架构(ARM、PowerPC)上,不显式指定 memory order 可能导致预期外的重排。比如:
std::atomicflag{false}; int data = 0; // 线程 A data = 42; flag.store(true, std::memory_order_relaxed); // ❌ 可能被重排到 data=42 之前! // 线程 B if (flag.load(std::memory_order_relaxed)) { std::cout << data << "\n"; // 可能输出 0! }
正确做法是用 std::memory_order_release / std::memory_order_acquire 配对,或直接用默认的 seq_cst。
真正难的不是写对第一行 std::atomic 声明,而是判断哪些地方需要加内存屏障、哪些变量必须一起约束——这往往取决于 CPU 架构和具体同步意图,不是查文档就能绕开的。









