std::atomic 保证单变量原子操作但不自动无锁,是否无锁取决于类型和平台,需用 is_lock_free() 检查;它不保证多变量一致性,不能替代 mutex 保护复杂状态,内存序必须显式指定。

std::atomic 能避免数据竞态,但不等于自动无锁
直接说结论:std::atomic 的核心优势是提供**对单个变量的线程安全读写语义**,编译器和 CPU 会保证其操作不可分割(atomic),从而防止因指令重排或非原子读写导致的数据竞态。但它是否“无锁”,取决于具体类型和平台——std::atomic 在 x86-64 上通常编译为 lock xadd 等 CPU 原语,本质是硬件级锁;而 std::atomic<:shared_ptr>> 或大尺寸类型(如 std::atomic<:array>>)可能退化为内部互斥量实现,此时就是“有锁”的。
常见误判场景:
- 以为所有
std::atomic都是 lock-free:实际需检查T::is_lock_free()或std::atomic{}.is_lock_free() - 用
++或fetch_add安全更新计数器,却在多个 atomic 变量间做“逻辑组合”(如先读 A 再读 B),仍可能产生竞态——atomic 保单变量,不保多变量间的一致性
比 std::mutex 轻量,但适用场景很窄
std::atomic 的开销远低于 std::mutex:没有系统调用、无上下文切换、无阻塞等待。但它只解决“单个标量/POD 类型”的原子访问问题,无法替代锁来保护复杂状态或临界区。
典型适用场景包括:
立即学习“C++免费学习笔记(深入)”;
- 引用计数(
std::shared_ptr内部就重度依赖std::atomic) - 标志位开关(
std::atomic用于通知线程退出) - 计数器累加(
std::atomic)counter{0}; counter.fetch_add(1, std::memory_order_relaxed) - 无锁数据结构的基础构件(如 CAS 循环实现的 stack、queue)
一旦涉及多个字段协同更新(比如“余额减去金额,同时记录交易时间戳”),就必须用 std::mutex 或更高级同步原语,std::atomic 无法覆盖这类需求。
内存序(memory order)不是可选项,而是必选项
忽略内存序参数是 C++ 原子操作最隐蔽的坑。默认的 std::memory_order_seq_cst 最安全但性能最差;而 std::memory_order_relaxed 虽快,却允许编译器和 CPU 重排前后普通读写——这会导致看似正确的代码在多核上行为异常。
std::atomicready{false}; int data = 0; // 线程 A data = 42; // 普通写 ready.store(true, std::memory_order_relaxed); // 原子写,但无顺序约束
// 线程 B while (!ready.load(std::memory_order_relaxed)) { / 自旋 / } std::cout << data << "\n"; // 可能输出 0!因为 data=42 可能被重排到 ready.store 之后
正确做法是至少使用 std::memory_order_acquire / std::memory_order_release 配对:
// 线程 A data = 42; ready.store(true, std::memory_order_release); // 保证 data=42 不会重排到它之后// 线程 B while (!ready.load(std::memory_order_acquire)) { / 自旋 / } std::cout << data << "\n"; // 此时 data=42 一定可见
std::atomic 的初始化和赋值容易踩坑
std::atomic 不支持拷贝构造和拷贝赋值,也不能用 = 直接赋值普通值(除非用 .store() 或隐式转换构造)。
- 错误写法:
std::atomic—— 这是合法的,但只是调用隐式构造函数,不是赋值a = 5; - 更危险的是:
a = 5;看似自然,实则调用operator=(int),等价于a.store(5),但容易让人误以为是普通赋值语义 - 真正易错的是:未显式初始化的
std::atomic是值不确定的(not zero-initialized),必须写成a; std::atomic或a{0}; std::atomic(C++17 起已弃用后者)a = ATOMIC_VAR_INIT(0); - 聚合类型(如
struct)不能直接作为std::atomic,除非T是 trivially copyable 且满足对齐要求;否则编译失败或运行时 fallback 到锁实现
真正难的从来不是“怎么写 atomic”,而是想清楚:这个变量是否真的只需原子读写?它的修改是否独立于其他状态?内存序是否匹配实际同步意图?这些问题没理清,std::atomic 只会让 bug 更难复现。










