C++11 std::atomic
是 C++ 并发支持库提供的原子类型模板。其核心目标是:主模版实例化条件的主模板 并非对任意类型开放。当且仅当以下条件全部满足时,程序才是良构的:可平凡复制 是 C++ 类型的一种属性,指该类型的对象可以通过简单的内存拷贝(如 memcpy) 完成复制,且复制后的对象与原对象完全等价、行为正常满足 Trivially Copyable 的条件当为标准整数类型或中定义的整数类型时, 额外提供:当为
std::atomic
是 C++ 并发支持库提供的原子类型模板。
其核心目标是:
- 在多线程环境中,对共享对象的并发读写提供良定义行为;
- 避免数据竞争(data race)导致的未定义行为;
- 通过
std::memory_order指定线程间同步关系与内存访问顺序。
若一个线程写入某个 atomic 对象,另一个线程并发读取该对象,则程序行为是良定义的。
模板定义
template< class T >
struct atomic; // since C++11
template< class U >
struct atomic<U*>; // since C++11
template< class U >
struct atomic<std::shared_ptr<U>>; // since C++20
template< class U >
struct atomic<std::weak_ptr<U>>; // since C++20
主模版实例化条件
std::atomic<T> 的主模板 并非对任意类型开放。
当且仅当以下条件全部满足时,程序才是良构的:
T为 TriviallyCopyableT可拷贝构造、可移动构造T可拷贝赋值、可移动赋值T不带const/volatile修饰
Trivially Copyable
可平凡复制 是 C++ 类型的一种属性,指该类型的对象可以通过简单的内存拷贝(如 memcpy) 完成复制,且复制后的对象与原对象完全等价、行为正常
满足 Trivially Copyable 的条件
- 没有自定义的复制 / 移动构造函数、复制 / 移动赋值运算符
- 没有自定义的析构函数
- 所有非静态成员变量都是 Trivially Copyable 类型
- 没有虚函数、没有虚基类
非 Trivially Copyable 类型的对象,不只是一段纯数据,还包含复杂的 “语义” 和 “资源关联”。memcpy只会复制字节,不会处理这些复杂语义,直接导致未定义行为
内建类型特化
整数类型特化
当 T 为标准整数类型或 <cstdint> 中定义的整数类型时,std::atomic<T> 额外提供:
- 原子算术:
fetch_add,fetch_sub - 原子位运算:
fetch_and,fetch_or,fetch_xor - 自增 / 自减运算符
- 对应的复合赋值运算符
浮点类型特化(C++20)
当 T 为 float、double、long double 或扩展浮点类型时:
- 提供
fetch_add/fetch_sub; - 即使结果不可表示,也不产生未定义行为;
指针特化
std::atomic<U*> 提供:
- 指针原子读写;
- 按元素单位的指针算术原子操作;
difference_type定义为std::ptrdiff_t。
成员类型(Member types)
| 成员类型 | 适用范围 |
|---|---|
value_type |
所有 std::atomic<T>,定义为 T |
difference_type |
仅存在于整数、浮点、指针特化 |
主模板、自定义类型、
shared_ptr/weak_ptr特化 不定义difference_type。
函数
通用函数
构造函数
提供两种核心构造方式,拷贝构造函数被显式删除
为避免并发语义被破坏,std::atomic 被设计为不可拷贝、不可移动
constexpr atomic() noexcept; // 默认构造,处于未指定状态,先显式赋值
constexpr atomic( T desired ) noexcept;
赋值运算符
重载了底层值类型的赋值运算符,赋值操作等价于调用 store 函数,同样拷贝赋值运算符被删除:
T operator=( T desired ) noexcept;
赋值运算符仅支持底层值类型, 不支持原子类型之间的赋值
is_lock_free
判断当前原子对象的操作是否为无锁实现:
- 无锁(lock-free):直接通过硬件指令(如 CPU 的原子指令)实现,性能极高,无线程阻塞。
- 有锁(non-lock-free):可能通过库/运行时回退机制实现,不保证无阻塞。
// 成员函数:运行时判断当前对象是否无锁
bool is_lock_free() const noexcept;
// C++17 新加静态成员常量
static constexpr bool is_always_lock_free = /* 实现定义 */;
return:
true:操作是无锁实现。false:操作是有锁实现。
store
将指定的值原子地存储到 std::atomic 对象中,覆盖原有值,操作不可分割。
void store( T desired, std::memory_order order = std::memory_order_seq_cst ) const noexcept;
order:内存序,控制操作的内存可见性,默认std::memory_order_seq_cst(最强内存序,保证全局顺序一致)。
load
原子地读取 std::atomic 对象的当前值,返回读取到的值,操作不可分割。
T load( std::memory_order order = std::memory_order_seq_cst ) const noexcept;
operator T() const noexcept;
代码示例
#include <atomic>
#include <iostream>
int main() {
std::atomic<int> a(100);
// 显式调用 load()
int val1 = a.load();
// 隐式转换,等价于 a.load()
int val2 = a;
std::cout << val1 << " " << val2 << std::endl; // 输出 100 100
return 0;
}
exchange
原子地执行 “读旧值 + 写新值”:将新值写入原子对象,同时返回写入前的旧值。整个操作不可分割,是 “先读后写” 的原子化实现。
T exchange( T desired, std::memory_order order = std::memory_order_seq_cst ) noexcept;
代码示例
#include <atomic>
#include <iostream>
int main() {
std::atomic<int> a(10);
int old_val = a.exchange(20);
std::cout << "旧值:" << old_val << ",新值:" << a.load() << "\n"; // 旧值:10,新值:20
return 0;
}
CAS
compare_exchange_weak / compare_exchange_strong
这两个函数是 CAS(Compare-And-Swap,比较并交换) 操作,是实现无锁数据结构(如无锁队列、无锁栈)的核心,也是最复杂的原子函数。
通用 CAS 原理
两个函数的核心逻辑完全一致,原子地执行以下步骤:
- 比较原子对象的当前值与
expected(预期值)。 - 如果相等:将原子对象的值替换为
desired,返回true。 - 如果不相等:将原子对象的当前值写入
expected变量,返回false。
整个过程不可分割,是 “比较 - 替换” 的原子化实现。
语法
// 指定成功/失败的内存序
bool compare_exchange_weak( T& expected, T desired,
std::memory_order success,
std::memory_order failure ) noexcept;
// 失败内存序自动推导
bool compare_exchange_weak( T& expected, T desired,
std::memory_order order = std::memory_order_seq_cst ) noexcept;
bool compare_exchange_strong( T& expected, T desired,
std::memory_order success,
std::memory_order failure ) noexcept;
bool compare_exchange_strong( T& expected, T desired,
std::memory_order order = std::memory_order_seq_cst ) noexcept;
区别
| 特性 | compare_exchange_weak | compare_exchange_strong |
|---|---|---|
| 伪失败 | 可能发生 | 绝不会发生 |
| 循环要求 | 必须配合循环使用 | 可单次使用,无需循环 |
| 性能 | 更高(多核平台优势明显) | 稍低(额外硬件检查开销) |
| 适用场景 | 循环重试的无锁操作(主流) | 单次尝试的一次性操作 |
代码示例
void inc_bad() {
for (int i = 0; i < 100000; ++i) {
int old = counter.load(std::memory_order_relaxed);
counter.store(old + 1, std::memory_order_relaxed);
// 读写间,可能存在插入
}
}
void inc_cas() {
for (int i = 0; i < 100000; ++i) {
int expected = counter.load(std::memory_order_relaxed);
while (!counter.compare_exchange_weak(
expected, expected + 1,
std::memory_order_relaxed,
std::memory_order_relaxed
)) {
// expected 在失败时会被写成“当前值”,继续尝试即可
}
}
}
特化成员函数
fetch_add / fetch_sub
整数、浮点、指针均支持
T fetch_add( T val, std::memory_order order = std::memory_order_seq_cst ) noexcept;
T fetch_sub( T val, std::memory_order order = std::memory_order_seq_cst ) noexcept;
- 参数
val:要加减的数值(整数 / 浮点)或偏移量(指针); - 参数
order:内存序,默认全局顺序一致; - 返回值:操作前的旧值(不是新值)。
++ / --
仅整数类型和指针类型支持 ++/-- 运算符重载.
本质是 fetch_add(1)/fetch_sub(1) 的语法糖,行为和普通 ++/-- 一致,但均为原子操作。
| 运算符 | 作用 | 返回值 | 等价操作 |
|---|---|---|---|
++atomic |
前置自增(先加,后返回) | 新值(引用) | atomic.fetch_add(1) + 1 |
atomic++ |
后置自增(先返回,后加) | 旧值 | atomic.fetch_add(1) |
--atomic |
前置自减(先减,后返回) | 新值(引用) | atomic.fetch_sub(1) - 1 |
atomic-- |
后置自减(先返回,后减) | 旧值 | atomic.fetch_sub(1) |
fetch_and、fetch_or、fetch_xor
仅整数类型(如 int/unsigned int/uint64_t)支持位运算特化函数,分别对应原子按位与、原子按位或、原子按位异或,是实现原子标志位、掩码操作的核心。
T fetch_and( T val, std::memory_order order = std::memory_order_seq_cst ) noexcept;
T fetch_or( T val, std::memory_order order = std::memory_order_seq_cst ) noexcept;
T fetch_xor( T val, std::memory_order order = std::memory_order_seq_cst ) noexcept;
- 参数
val:位运算的掩码值; - 返回值:操作前的旧值。
std::memory_order
用于约束原子操作与其他内存访问之间的可见性与顺序关系。
用来在“没有 mutex 的情况下”,精确描述“哪些写对哪些读可见”。
内存模型中的三个核心概念
Modification Order(修改序)
- 对同一个 atomic 对象的所有写操作,所有线程观察到的顺序是一致的
- 即使使用
memory_order_relaxed,这一点仍然成立
这保证了 atomic 不会出现“不同线程看到完全不一致的值历史”
Happens-Before
happens-before 是 C++ 内存模型中唯一能保证可见性的关系。
若 A happens-before B,则:
- A 的所有内存写入,对 B 必然可见
- 编译器与 CPU 不得重排 A 到 B 之后
memory_order的本质作用:
是否、以及如何建立 happens-before
Synchronizes-With
synchronizes-with 是 happens-before 的一种特殊来源,
最常见的就是:
store(memory_order_release)
⟶load(memory_order_acquire)
std::memory_order 的枚举值
enum memory_order {
memory_order_relaxed,
memory_order_consume, // 基本弃用
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,
memory_order_seq_cst
};
memory_order_relaxed
- 仅保证:
- 原子性
- 对同一 atomic 对象的修改序一致
- 不建立任何 happens-before 关系
- 不对普通内存访问施加顺序约束
memory_order_acquire
- 本线程中:
- acquire 之后 的所有读写
- 不得被重排到 acquire 之前
- 若读取到了由
release写入的值:- 与该 release 建立
synchronizes-with
- 与该 release 建立
memory_order_release
- 本线程中:
- release 之前 的所有读写
- 不得被重排到 release 之后
- 若被 acquire 读取:
- 与 acquire 建立同步
memory_order_acq_rel
- 同时具备:
- acquire(禁止向前重排)
- release(禁止向后重排)
memory_order_seq_cst
- 所有
seq_cst原子操作:- 在全局上形成单一总序
- 每个线程观察到的顺序一致
- 同时具备 acquire + release 语义
示例
#include <atomic>
#include <thread>
#include <iostream>
#include <cassert>
struct Payload {
int x;
int y;
};
Payload g_data; // 普通共享数
std::atomic<bool> g_ready{false}; // 发布标志
void producer() {
// 写入
g_data.x = 42;
g_data.y = 7;
// 发布, release 保证之前的任何写入不会被重排到 store 之后
g_ready.store(true, std::memory_order_release);
}
void consumer() {
// 订阅。acquire 保证后续任何读取不会被重排到 load 之前
while (!g_ready.load(std::memory_order_acquire)) {
// ...
}
// 一旦读到 true,就“同步到” producer 的写入
assert(g_data.x == 42);
assert(g_data.y == 7);
std::cout << "x=" << g_data.x << ", y=" << g_data.y << "\n";
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
}
发布–订阅模式里,原子变量通常不是“承载数据”,而是“承载同步”。数据放普通内存,通过 release/acquire 把整批写入一次性发布出去。
同时也不存在数据竞争
能力边界
std::atomic 提供的是单个对象级别的原子性与有限的线程间同步能力,用于在不引入互斥锁的情况下安全地读写共享状态。
通过合适的 std::memory_order,原子操作可以建立 happens-before 关系,从而实现发布–订阅等基础同步模式。
更多推荐


所有评论(0)