C++ 中 std::atomic 详解:从原子操作到无锁编程

发布时间:2026/7/25 17:00:03
C++ 中 std::atomic 详解:从原子操作到无锁编程 C 中 std::atomic 详解从原子操作到无锁编程一、引言多线程世界的确定性操作在多线程编程中最隐蔽、最难调试的 bug 之一就是数据竞争(Data Race)——两个线程同时访问同一内存位置至少一个是写操作且没有同步机制。数据竞争导致的行为是未定义的程序可能崩溃、产生错误结果或者更糟——表面正确但随时可能出错。std::atomic是 C11 引入的解决数据竞争的基础设施。它将变量的读写操作提升为原子操作——一个不可被中断、不可被分割的完整操作。在多线程环境中对std::atomic对象的操作总是无数据竞争的不需要额外的互斥锁保护。二、核心概念速览| 维度 | 说明 || --- | --- || 原子性(Atomicity) | 操作不可分割——要么完全执行要么完全不执行不会出现中间状态 || 数据竞争 | 两个线程同时访问同一内存至少一个写且无同步 → 未定义行为 || 内存序(Memory Order) | 控制原子操作在多大程度上约束编译器和 CPU 的指令重排 || 无锁(Lock-Free) | 大多数 atomic 类型使用 CPU 原子指令实现不依赖操作系统锁 || CAS | Compare-And-Swap(比较并交换)最重要的原子操作原语 |三、为什么需要 atomic3.1 非原子操作的经典问题cpp复制下载#include thread #include iostream int counter 0; // 非原子变量 void increment() { for (int i 0; i 100000; i) { counter; // ❌ 数据竞争 // counter 实际上分为三步 // 1. 从内存读取 counter 到寄存器 // 2. 寄存器值 1 // 3. 将寄存器值写回内存 // 两个线程可能交错执行这三步导致丢失更新 } } int main() { std::thread t1(increment); std::thread t2(increment); t1.join(); t2.join(); std::cout Counter: counter std::endl; // 期望 200000实际结果不确定通常小于 200000 }图表代码下载全屏3.2 使用 atomic 解决cpp复制下载#include atomic #include thread #include iostream std::atomicint counter{0}; // 原子变量 void increment() { for (int i 0; i 100000; i) { counter; // ✓ 原子操作无数据竞争 } } int main() { std::thread t1(increment); std::thread t2(increment); t1.join(); t2.join(); std::cout Counter: counter std::endl; // 200000 }四、atomic 的基本操作4.1 核心 APIcpp复制下载#include atomic #include iostream int main() { std::atomicint value{0}; // 基本读写 value.store(42); // 原子写入 int x value.load(); // 原子读取 // 常用操作 value 100; // 等价于 store(100) int y value; // 等价于 load() // 读-改-写操作 int old value.fetch_add(10); // 原子地old value; value 10; int prev value; // 原子地prev value; value 1; int next value; // 原子地value 1; next value; value.fetch_sub(5); // 原子地 value - 5 value 0xFF; // 原子地 value value 0xFF value | 0x80; // 原子地 value value | 0x80 // 交换操作 int oldValue value.exchange(999); // 原子地oldValue value; value 999; // CAS (Compare-And-Swap) int expected 999; bool success value.compare_exchange_strong(expected, 1000); // 如果 value expected则 value 1000返回 true // 否则 expected value返回 false std::cout Final value: value.load() std::endl; }4.2 关键操作详解| 操作 | 说明 | 是否原子 || --- | --- | --- || load() / store(val) | 读取 / 写入 | 是 || exchange(val) | 写入新值返回旧值 | 是 || fetch_add(n) / fetch_sub(n) | 增加/减少返回旧值 | 是 || operator / operator-- | 前置/后置自增减 | 是 || compare_exchange_strong(expected, desired) | 如果值等于 expected则设为 desired | 是 || compare_exchange_weak(expected, desired) | 同上但允许伪失败 | 是 || is_lock_free() | 检查是否使用 CPU 原子指令 | — |五、Compare-And-Swap (CAS)5.1 CAS 的工作原理CAS 是无锁编程的基石。它原子地执行“如果当前值等于我期望的值就更新为新值否则不更新。”cpp复制下载#include atomic #include iostream int main() { std::atomicint value{100}; int expected 100; int desired 200; // 尝试将 value 从 100 改为 200 if (value.compare_exchange_strong(expected, desired)) { std::cout CAS succeeded: value is now value std::endl; } else { std::cout CAS failed: value is value , expected updated to expected std::endl; } // 如果失败expected 会被更新为 value 的当前值 // 可以基于新的 expected 重试 while (!value.compare_exchange_weak(expected, desired)) { // expected 自动更新循环重试直到成功 desired expected * 2; // 基于新值计算 desired } }5.2 strong vs weak| 版本 | 行为 | 适用场景 || --- | --- | --- || compare_exchange_strong | 保证只在值不等时才失败 | 简单直接的 CAS 操作 || compare_exchange_weak | 即使值相等也可能伪失败 | 循环重试中性能更好 |cpp复制下载// weak 版本的正确用法在循环中使用 std::atomicint val{0}; int expected val.load(); while (!val.compare_exchange_weak(expected, expected 1)) { // expected 被自动更新重试即可 // 即使伪失败循环也会正确处理 }六、内存序(Memory Order)6.1 内存序的概念内存序控制原子操作在多线程环境下的可见性顺序。它是一把双刃剑越严格的顺序保证越安全但越慢越宽松的顺序性能越好但需要更仔细的设计。cpp复制下载#include atomic #include thread #include iostream std::atomicbool ready{false}; std::atomicint data{0}; // 线程 A生产者 void producer() { data.store(42, std::memory_order_relaxed); // 1. 先准备数据 ready.store(true, std::memory_order_release); // 2. 发布就绪信号 } // 线程 B消费者 void consumer() { while (!ready.load(std::memory_order_acquire)); // 3. 获取就绪信号 std::cout data.load(std::memory_order_relaxed) std::endl; // 4. 读取数据 // 由于 release-acquire 的同步关系 // 4 保证能看到 1 写入的 42 }6.2 六种内存序图表代码下载全屏6.3 内存序选择指南| 内存序 | 性能 | 保证 | 典型场景 || --- | --- | --- | --- || seq_cst默认 | 最慢 | 全局顺序一致性 | 不确定用什么时的安全选择 || acquire | 较快 | 后续操作不能前移 | 读锁、消费数据 || release | 较快 | 之前操作不能后移 | 写锁、发布数据 || acq_rel | 中 | 同时具有 acquire 和 release | 单个原子 RMW 操作 || relaxed | 最快 | 仅原子性 | 简单计数器、无依赖关系 |七、atomic 的实际应用7.1 无锁计数器cpp复制下载#include atomic #include thread #include vector #include iostream class ThreadSafeCounter { std::atomiclong long count_{0}; public: void increment() { count_.fetch_add(1, std::memory_order_relaxed); // 只需原子性 } long long get() const { return count_.load(std::memory_order_relaxed); } }; int main() { ThreadSafeCounter counter; std::vectorstd::thread threads; for (int i 0; i 10; i) { threads.emplace_back([counter]() { for (int j 0; j 100000; j) { counter.increment(); } }); } for (auto t : threads) t.join(); std::cout Count: counter.get() std::endl; // 1,000,000 }7.2 自旋锁(Spinlock)cpp复制下载#include atomic #include thread class Spinlock { std::atomic_flag flag_ ATOMIC_FLAG_INIT; // 最简单的原子类型 public: void lock() { // 自旋直到成功设置标志 while (flag_.test_and_set(std::memory_order_acquire)) { // CPU 提示正在自旋可选降低功耗 #if defined(__cpp_lib_atomic_wait) flag_.wait(true, std::memory_order_relaxed); #endif } } void unlock() { flag_.clear(std::memory_order_release); #if defined(__cpp_lib_atomic_wait) flag_.notify_one(); #endif } }; // 使用自旋锁保护临界区 Spinlock spin; int sharedData 0; void safeIncrement() { spin.lock(); sharedData; // 临界区 spin.unlock(); }7.3 无锁链表节点cpp复制下载#include atomic #include memory #include iostream templatetypename T class LockFreeStack { struct Node { T data; Node* next; Node(const T val) : data(val), next(nullptr) { } }; std::atomicNode* head_{nullptr}; public: void push(const T value) { Node* newNode new Node(value); newNode-next head_.load(std::memory_order_relaxed); // CAS 循环尝试将 head 更新为 newNode while (!head_.compare_exchange_weak( newNode-next, newNode, std::memory_order_release, std::memory_order_relaxed)) { // CAS 失败时newNode-next 已被更新为当前 head // 重试即可 } } bool pop(T result) { Node* oldHead head_.load(std::memory_order_relaxed); while (oldHead ! nullptr) { if (head_.compare_exchange_weak( oldHead, oldHead-next, std::memory_order_acquire, std::memory_order_relaxed)) { result oldHead-data; delete oldHead; // 注意此时其他线程可能仍持有旧指针 return true; } } return false; } ~LockFreeStack() { T dummy; while (pop(dummy)) { } } };7.4 单例模式cpp复制下载#include atomic #include memory #include mutex class Singleton { public: static Singleton getInstance() { // 第一次检查无锁快速路径 Singleton* instance instance_.load(std::memory_order_acquire); if (!instance) { std::lock_guardstd::mutex lock(mutex_); // 第二次检查持锁防止重复创建 instance instance_.load(std::memory_order_relaxed); if (!instance) { instance new Singleton(); instance_.store(instance, std::memory_order_release); } } return *instance; } private: Singleton() default; static std::atomicSingleton* instance_; static std::mutex mutex_; }; std::atomicSingleton* Singleton::instance_{nullptr}; std::mutex Singleton::mutex_;八、atomic vs mutex 对比| 维度 | std::atomic | std::mutex || --- | --- | --- || 操作粒度 | 单个变量 | 代码块 || 阻塞行为 | 无阻塞 | 可能阻塞 || 开销 | 极低CPU 指令级别 | 较高可能系统调用 || 适用场景 | 简单计数器、标志位 | 复杂临界区 || 编程难度 | 高需理解内存序 | 低 || 死锁风险 | 无 | 有 |九、选择决策流程图表代码下载全屏十、总结std::atomic是 C 中实现线程安全操作的基础工具理解它需要把握以下要点原子性的本质原子操作是不可分割的——在多线程环境中对atomic变量的操作要么完全执行要么完全不执行不存在中间状态被其他线程观察到的情况。这是消除数据竞争的根本保证。核心操作load/store基本读写fetch_add/fetch_sub原子算术操作exchange交换值compare_exchange_strong/weak(CAS)比较并交换无锁编程的基石内存序的权衡seq_cst默认最安全性能最低。不确定时使用acquire/release成对使用实现同步性能较好relaxed仅保证原子性性能最高。适用于简单计数器降低内存序要求可以显著提升性能但需要仔细分析线程间的依赖关系适用场景首选场景简单的计数器、布尔标志、指针赋值谨慎使用CAS 循环实现无锁数据结构需要深入理解内存序和 ABA 问题不适合的场景需要同时修改多个变量、复杂的业务逻辑应使用mutex最佳实践默认使用seq_cst确认安全后逐步降低内存序要求CAS 循环使用compare_exchange_weak以获得更好的性能使用is_lock_free()检查原子操作的实现方式避免在atomic和mutex之间过度优化——简单正确的代码往往比微妙的优化更重要std::atomic是 C 并发编程的基础模块它既是构建高性能无锁数据结构的基石也是日常多线程编程中替代mutex的轻量级选择。掌握它就能在多线程世界中写出正确且高效的代码。