C++ 中 std::atomic 详解:从原子操作到无锁编程
2026/7/25 16:59:55 网站建设 项目流程

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



一、引言:多线程世界的确定性操作


在多线程编程中,最隐蔽、最难调试的 bug 之一就是数据竞争(Data Race)——两个线程同时访问同一内存位置,至少一个是写操作,且没有同步机制。数据竞争导致的行为是未定义的,程序可能崩溃、产生错误结果,或者更糟——表面正确但随时可能出错。


std::atomic是 C++11 引入的解决数据竞争的基础设施。它将变量的读写操作提升为原子操作——一个不可被中断、不可被分割的完整操作。在多线程环境中,对std::atomic对象的操作总是无数据竞争的,不需要额外的互斥锁保护。



二、核心概念速览



| 维度 | 说明 |

| --- | --- |

| 原子性(Atomicity) | 操作不可分割——要么完全执行,要么完全不执行,不会出现中间状态 |

| 数据竞争 | 两个线程同时访问同一内存,至少一个写,且无同步 → 未定义行为 |

| 内存序(Memory Order) | 控制原子操作在多大程度上约束编译器和 CPU 的指令重排 |

| 无锁(Lock-Free) | 大多数 atomic 类型使用 CPU 原子指令实现,不依赖操作系统锁 |

| CAS | Compare-And-Swap(比较并交换),最重要的原子操作原语 |



三、为什么需要 atomic



3.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::atomic<int> 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 核心 API


cpp复制下载

#include <atomic> #include <iostream> int main() { std::atomic<int> 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::atomic<int> 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::atomic<int> 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::atomic<bool> ready{false}; std::atomic<int> 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::atomic<long 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::vector<std::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> template<typename T> class LockFreeStack { struct Node { T data; Node* next; Node(const T& val) : data(val), next(nullptr) { } }; std::atomic<Node*> 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_guard<std::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::atomic<Singleton*> instance_; static std::mutex mutex_; }; std::atomic<Singleton*> Singleton::instance_{nullptr}; std::mutex Singleton::mutex_;



八、atomic vs mutex 对比



| 维度 | std::atomic | std::mutex |

| --- | --- | --- |

| 操作粒度 | 单个变量 | 代码块 |

| 阻塞行为 | 无阻塞 | 可能阻塞 |

| 开销 | 极低(CPU 指令级别) | 较高(可能系统调用) |

| 适用场景 | 简单计数器、标志位 | 复杂临界区 |

| 编程难度 | 高(需理解内存序) | 低 |

| 死锁风险 | 无 | 有 |



九、选择决策流程


图表代码下载全屏

十、总结


std::atomic是 C++ 中实现线程安全操作的基础工具,理解它需要把握以下要点:



  1. 原子性的本质:原子操作是不可分割的——在多线程环境中,对atomic变量的操作要么完全执行,要么完全不执行,不存在中间状态被其他线程观察到的情况。这是消除数据竞争的根本保证。
  2. 核心操作



  • load/store:基本读写
  • fetch_add/fetch_sub:原子算术操作
  • exchange:交换值
  • compare_exchange_strong/weak(CAS):比较并交换,无锁编程的基石
  1. 内存序的权衡



  • seq_cst(默认):最安全,性能最低。不确定时使用
  • acquire/release:成对使用实现同步,性能较好
  • relaxed:仅保证原子性,性能最高。适用于简单计数器
  • 降低内存序要求可以显著提升性能,但需要仔细分析线程间的依赖关系
  1. 适用场景



  • 首选场景:简单的计数器、布尔标志、指针赋值
  • 谨慎使用:CAS 循环实现无锁数据结构(需要深入理解内存序和 ABA 问题)
  • 不适合的场景:需要同时修改多个变量、复杂的业务逻辑(应使用mutex
  1. 最佳实践



  • 默认使用seq_cst,确认安全后逐步降低内存序要求
  • CAS 循环使用compare_exchange_weak以获得更好的性能
  • 使用is_lock_free()检查原子操作的实现方式
  • 避免在atomicmutex之间过度优化——简单正确的代码往往比微妙的优化更重要


std::atomic是 C++ 并发编程的基础模块,它既是构建高性能无锁数据结构的基石,也是日常多线程编程中替代mutex的轻量级选择。掌握它,就能在多线程世界中写出正确且高效的代码。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询