C++ 11 atomic 原子性操作
是模板类,支持大部分基础数据类型(boolcharintlong、指针等),也支持用户自定义的 “可平凡复制” 类型(Trivially Copyable,即能通过 memcpy 复制的类型,如不含指针的结构体)。cpp// 1. 基础类型原子变量// 原子布尔(标志位)// 原子整型(计数器)// 原子长整型// 原子指针// 2. 自定义平凡类型(需 C++20 或编译器支持,部分编译器 C+
std::atomic 是 C++11 引入的原子操作库,定义在 <atomic> 头文件中,核心作用是提供无锁(Lock-Free)的线程安全访问,避免多线程并发修改共享变量时出现的数据竞争(Data Race)。它底层依赖 CPU 原子指令(如 CAS、Load/Store 屏障),无需显式加锁(如互斥锁、自旋锁),就能保证单个操作的原子性、可见性和有序性,是实现高效并发的基础工具。
在多线程环境中,普通共享变量的读写可能存在 “数据竞争”—— 例如线程 A 写入 i = 1,线程 B 读取 i,由于 CPU 指令重排序、缓存一致性等问题,线程 B 可能读取到 “中间值”(尤其是非单个 CPU 指令能完成的操作,如 i++)。
传统解决方案是用锁(如 std::mutex)保护临界区,但锁会带来上下文切换开销;而 std::atomic 直接通过 CPU 原子指令实现,无锁开销、延迟更低,适用于 “单个变量的简单同步” 场景(如计数器、标志位)。
std::atomic 保证三大核心特性(并发安全的基石):
- 原子性:操作是 “不可分割” 的,不会被其他线程打断(如
atomic_var++不会出现 “一半执行、一半被抢占” 的情况); - 可见性:一个线程对原子变量的修改,会立即被其他线程感知(避免缓存导致的 “脏读”);
- 有序性:默认禁止编译器和 CPU 对原子操作进行重排序(可通过内存序参数灵活控制)。
接下来我们来讲一下其用法
基本用法:原子变量的定义与操作
1. 原子变量的定义
std::atomic 是模板类,支持大部分基础数据类型(bool、char、int、long、指针等),也支持用户自定义的 “可平凡复制” 类型(Trivially Copyable,即能通过 memcpy 复制的类型,如不含指针的结构体)。
常见定义方式:
cpp
#include <atomic>
#include <thread>
#include <iostream>
// 1. 基础类型原子变量
std::atomic<bool> flag(false); // 原子布尔(标志位)
std::atomic<int> counter(0); // 原子整型(计数器)
std::atomic<long long> big_counter(0); // 原子长整型
std::atomic<int*> ptr(nullptr); // 原子指针
// 2. 自定义平凡类型(需 C++20 或编译器支持,部分编译器 C++11 即可)
struct Point {
int x;
int y;
// 必须是平凡复制类型(无自定义拷贝构造/赋值运算符、析构函数)
};
std::atomic<Point> atomic_point{{1, 2}}; // 原子结构体
关键注意:
- 原子变量不能被拷贝或赋值(拷贝构造和赋值运算符被禁用),只能通过原子操作修改;
- 若自定义类型非平凡复制(如含
std::string、指针),使用std::atomic<T>会编译失败(需用std::atomic_ref或锁保护)。
2. 核心原子操作
std::atomic 提供了两类操作接口:成员函数(更灵活)和全局函数(兼容 C 风格),核心操作如下:
(1)读 / 写操作(load/store)
load(memory_order = std::memory_order_seq_cst):原子读取变量值,返回类型为T;store(T val, memory_order = std::memory_order_seq_cst):原子写入变量值,无返回;- 内存序(
memory_order)默认是seq_cst(顺序一致),后续会详细解释。
void write_flag() {
flag.store(true); // 原子写入 true
}
void read_flag() {
bool val = flag.load(); // 原子读取 flag 值
if (val) {
std::cout << "Flag is true\n";
}
}
int main() {
std::thread t1(write_flag);
std::thread t2(read_flag);
t1.join();
t2.join();
return 0;
}
(2)自增 / 自减操作(针对数值类型)
std::atomic<int>、std::atomic<long> 等数值类型支持原子自增 / 自减,无需手动实现 CAS:
| 成员函数 | 功能 | 等价操作(非原子) |
|---|---|---|
fetch_add(T val) |
原子 += val,返回旧值 | old = x; x += val |
fetch_sub(T val) |
原子 -= val,返回旧值 | old = x; x -= val |
operator++() |
前缀自增(返回新值) | ++x |
operator++(int) |
后缀自增(返回旧值) | x++ |
operator--() |
前缀自减(返回新值) | --x |
operator--(int) |
后缀自减(返回旧值) | x-- |
示例:原子计数器(多线程安全累加)
const int THREAD_NUM = 10;
std::atomic<int> counter(0);
void increment() {
for (int i = 0; i < 10000; ++i) {
counter.fetch_add(1); // 原子自增,返回旧值(此处无需旧值)
// 等价写法:counter++ 或 ++counter
}
}
int main() {
std::thread threads[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; ++i) {
threads[i] = std::thread(increment);
}
for (auto& t : threads) {
t.join();
}
std::cout << "Final counter: " << counter << std::endl; // 必然输出 100000
return 0;
}
- 若用普通
int counter,多线程累加会出现数据竞争,最终结果小于 100000; std::atomic<int>保证每次fetch_add是原子操作,无需加锁即可安全并发。
(3)CAS 操作(compare_exchange_weak/strong)
CAS(Compare-And-Swap)是原子操作的核心,std::atomic 提供 compare_exchange_weak 和 compare_exchange_strong 两个成员函数,功能是:
原子性比较当前值与
expected(预期值):
- 若相等:将当前值更新为
desired(目标值),返回true;- 若不相等:将
expected更新为当前值,返回false。
函数签名:
cpp
// 弱版本:可能出现“伪失败”(当前值 == expected 但返回 false,需循环重试)
bool compare_exchange_weak(T& expected, T desired,
memory_order success, memory_order failure);
bool compare_exchange_weak(T& expected, T desired,
memory_order order = std::memory_order_seq_cst);
// 强版本:无伪失败,当前值 == expected 时必返回 true(性能略低于弱版本)
bool compare_exchange_strong(T& expected, T desired,
memory_order success, memory_order failure);
bool compare_exchange_strong(T& expected, T desired,
memory_order order = std::memory_order_seq_cst);
示例:用 CAS 实现自定义原子操作(如原子最大值更新)
cpp
std::atomic<int> max_val(0);
// 多线程尝试更新 max_val 为当前值与 new_val 中的较大者
void update_max(int new_val) {
int expected = max_val.load(); // 读取当前预期值
// 弱版本需循环:处理伪失败和并发修改
while (new_val > expected && !max_val.compare_exchange_weak(expected, new_val)) {
// 若失败,expected 已被更新为当前 max_val,重新进入循环比较
}
}
int main() {
std::thread t1(update_max, 10);
std::thread t2(update_max, 20);
std::thread t3(update_max, 15);
t1.join();
t2.join();
t3.join();
std::cout << "Max value: " << max_val << std::endl; // 输出 20
return 0;
}
弱版本 vs 强版本选择:
- 弱版本:适合 “循环重试” 场景(如上述示例),性能更高(尤其在 ARM、Power 等弱内存模型 CPU 上);
- 强版本:适合 “无需重试” 的场景(如单次尝试更新),避免伪失败导致的逻辑错误。
3. 内存序(memory_order):灵活控制并发顺序
std::atomic 的所有原子操作都支持可选的 memory_order 参数,用于控制操作的内存可见性和指令重排序规则。默认的 std::memory_order_seq_cst(顺序一致)是最严格的,能保证所有线程看到的原子操作顺序完全一致,但性能开销较高。
实际开发中可根据需求选择更宽松的内存序,平衡性能和正确性:
| 内存序类型 | 核心语义 | 适用场景 |
|---|---|---|
memory_order_seq_cst |
顺序一致:所有原子操作按全局统一顺序执行(默认) | 需严格顺序的场景(如全局计数器) |
memory_order_acquire |
读操作:禁止后续指令重排序到该操作之前;保证该操作读取的值是最新的 | 读取共享资源后,后续操作依赖该值 |
memory_order_release |
写操作:禁止之前的指令重排序到该操作之后;保证该操作的修改对其他线程可见 | 写入共享资源后,之前的修改需同步 |
memory_order_acq_rel |
读写操作:同时具备 acquire 和 release 语义(如 CAS 成功时) | 读写一体的操作(如 fetch_add) |
memory_order_relaxed |
松散:仅保证操作本身的原子性,不保证可见性和有序性 | 无依赖的独立操作(如统计次数) |
示例:用 acquire/release 优化性能(生产者 - 消费者模型)
cpp
std::atomic<bool> ready(false); // 生产者是否完成
std::atomic<int> data(0); // 共享数据
// 生产者线程:写入数据后,标记 ready 为 true
void producer() {
data.store(100, std::memory_order_relaxed); // 数据写入无需顺序约束
ready.store(true, std::memory_order_release); // release:data 的修改对消费者可见
}
// 消费者线程:等待 ready 为 true 后读取数据
void consumer() {
// acquire:读取 ready 为 true 时,确保 data 的修改已可见
while (!ready.load(std::memory_order_acquire)) {
// 自旋等待
}
std::cout << "Data: " << data.load(std::memory_order_relaxed) << std::endl; // 输出 100
}
int main() {
std::thread t_prod(producer);
std::thread t_cons(consumer);
t_prod.join();
t_cons.join();
return 0;
}
- 生产者的
ready.store(release)保证:data.store一定在ready.store之前执行,且data的修改对消费者可见; - 消费者的
ready.load(acquire)保证:data.load一定在ready.load之后执行,且能读取到生产者写入的data; - 相比默认的
seq_cst,acquire/release更宽松,性能更高。
三、std::atomic 的关键特性与限制
1. 核心特性
- 无锁性(Lock-Free):大多数
std::atomic特化版本(如bool、int、指针)是 “无锁” 的,可通过is_lock_free()成员函数判断:cpp
std::atomic<int> a; std::cout << "Is lock-free? " << (a.is_lock_free() ? "Yes" : "No") << std::endl; // 通常输出 Yes
若类型过大(如自定义结构体超过 CPU 原子操作支持的宽度),std::atomic可能退化为 “有锁实现”(底层用互斥锁),此时is_lock_free()返回false。 - 不可拷贝 / 赋值:原子变量的拷贝构造、赋值运算符被删除,只能通过
load()/store()或原子操作传递值:cpp
std::atomic<int> a(10); // std::atomic<int> b = a; // 编译失败:拷贝构造禁用 int b = a.load(); // 正确:通过 load 读取值 - 支持指针类型:
std::atomic<T*>可实现原子指针操作(如原子修改指针指向、原子fetch_add实现数组元素访问):cpp
int arr[5] = {1,2,3,4,5}; std::atomic<int*> p(arr); p.fetch_add(1); // 原子指针自增,指向 arr[1] std::cout << *p.load() << std::endl; // 输出 2
2. 常见限制
- 不支持复杂操作的原子性:
std::atomic仅保证 “单个操作” 的原子性,若需多个原子操作组成 “原子序列”(如 “读取 - 修改 - 写入” 多步),需手动用 CAS 循环或锁保护:cpp
// 错误:两个原子操作,整体非原子(可能被其他线程打断) if (counter.load() < 10) { counter.fetch_add(1); } // 正确:用 CAS 循环保证原子性 int expected = counter.load(); while (expected < 10 && !counter.compare_exchange_weak(expected, expected + 1)) {} - 自定义类型需平凡复制:若自定义类型
T非平凡复制(如含std::vector、自定义析构函数),std::atomic<T>会编译失败(C++20 可通过std::atomic_ref解决); - 内存序使用需谨慎:宽松内存序(如
relaxed)若使用不当,会导致数据可见性问题,需明确操作间的依赖关系。
四、std::atomic vs 锁(std::mutex):选择场景
| 对比维度 | std::atomic | std::mutex(互斥锁) |
|---|---|---|
| 适用场景 | 单个变量的简单同步(计数器、标志位、指针) | 复杂临界区(多个变量、多步操作) |
| 性能开销 | 低(无锁,依赖 CPU 原子指令) | 高(上下文切换、锁竞争开销) |
| 原子性范围 | 单个操作(load/store、fetch_add、CAS 等) | 整个临界区(多个操作组成的序列) |
| 易用性 | 简单(接口直观,无需手动加解锁) | 复杂(需手动管理 lock/unlock,避免死锁) |
| 灵活性 | 低(仅支持单个变量操作) | 高(支持任意临界区逻辑) |
选择建议:
- 若只需同步 “单个变量”(如计数器、开关标志),优先用
std::atomic(无锁、高效); - 若需同步 “多个变量” 或 “多步操作”(如修改结构体的多个字段、读取后写入新值并更新其他变量),用
std::mutex或std::unique_lock(保证临界区原子性)。
五、实战场景:std::atomic 的典型应用
1. 原子计数器(多线程统计)
cpp
#include <atomic>
#include <thread>
#include <vector>
#include <iostream>
std::atomic<size_t> request_count(0);
void handle_requests() {
for (int i = 0; i < 100000; ++i) {
++request_count; // 原子自增,统计请求数
}
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 8; ++i) {
threads.emplace_back(handle_requests);
}
for (auto& t : threads) {
t.join();
}
std::cout << "Total requests: " << request_count << std::endl; // 800000
return 0;
}
2. 线程安全的单例模式(无锁)
cpp
#include <atomic>
#include <memory>
class Singleton {
public:
static Singleton& get_instance() {
// 第一次检查:无锁,快速判断是否已初始化(relaxed 足够,仅需原子性)
Singleton* ptr = instance.load(std::memory_order_relaxed);
if (!ptr) {
// 第二次检查:加锁,避免多线程同时初始化
std::lock_guard<std::mutex> lock(mtx);
ptr = instance.load(std::memory_order_relaxed);
if (!ptr) {
ptr = new Singleton();
// release:确保 Singleton 构造完成后,才更新 instance
instance.store(ptr, std::memory_order_release);
}
}
// acquire:确保 instance 指向的对象已完全构造
return *instance.load(std::memory_order_acquire);
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
static std::atomic<Singleton*> instance;
static std::mutex mtx;
};
std::atomic<Singleton*> Singleton::instance(nullptr);
std::mutex Singleton::mtx;
- 用
std::atomic<Singleton*>原子存储单例指针,避免 “双重检查锁定”(DCLP)的经典问题; - 结合
acquire/release内存序,保证单例对象构造完成后才对其他线程可见。
3. 无锁队列的核心(基于 CAS)
std::atomic 是无锁数据结构(如无锁队列、无锁栈)的基础,核心是用 CAS 操作原子修改队列的头 / 尾指针:
cpp
template <typename T>
class LockFreeQueue {
private:
struct Node {
T data;
std::atomic<Node*> next;
Node(T val) : data(val), next(nullptr) {}
};
std::atomic<Node*> head;
std::atomic<Node*> tail;
public:
LockFreeQueue() : head(new Node(T())), tail(head.load()) {}
void push(T val) {
Node* new_node = new Node(val);
Node* old_tail = tail.load(std::memory_order_relaxed);
// CAS 更新 tail->next 为新节点
while (!old_tail->next.compare_exchange_weak(
nullptr, new_node,
std::memory_order_release,
std::memory_order_relaxed)) {
old_tail = tail.load(std::memory_order_relaxed);
}
// 更新 tail 指针
tail.compare_exchange_strong(old_tail, new_node, std::memory_order_release);
}
// 省略 pop 实现(类似 push,需原子修改 head 指针)
};更多推荐



所有评论(0)