原子操作
- [一、原子操作与 C++ 内存模型概述](#一、原子操作与 C++ 内存模型概述)
- [二、std::atomic 基本用法(C++11--17)](#二、std::atomic 基本用法(C++11–17))
-
- [1. 头文件与模板](#1. 头文件与模板)
- [2. Lock‐free 保证](#2. Lock‐free 保证)
- [3. 常用操作接口](#3. 常用操作接口)
-
- [1. store / load](#1. store / load)
- [2. exchange](#2. exchange)
- [3. compare_exchange_weak / compare_exchange_strong](#3. compare_exchange_weak / compare_exchange_strong)
- [4. fetch_add / fetch_sub / fetch_and / fetch_or / fetch_xor](#4. fetch_add / fetch_sub / fetch_and / fetch_or / fetch_xor)
- [5. atomic_thread_fence](#5. atomic_thread_fence)
- [6. std::atomic_flag 的专用操作](#6. std::atomic_flag 的专用操作)
- 小结
- [四、C++17 中的改进点](#四、C++17 中的改进点)
- [五、C++20/23 的新特性](#五、C++20/23 的新特性)
-
- [1. std::atomic_ref(C++20)](#1. std::atomic_ref(C++20))
- [2. 原子等待/通知(C++20)](#2. 原子等待/通知(C++20))
- [3. 并发构件(C++20/23,<latch>, <barrier>, <semaphore>)](#3. 并发构件(C++20/23,<latch>, <barrier>, <semaphore>))
- [4. 其他细节](#4. 其他细节)
- 六、使用建议与注意事项
一、原子操作与 C++ 内存模型概述
-
原子性 (Atomicity)
原子操作在多线程并发下要么全部完成,要么全部不做,不会被线程调度中断。
-
可见性 (Visibility)
一个线程对内存的写入,如果不通过原子操作或合适的内存屏障,其他线程可能无法及时看到。
-
有序性 (Ordering)
在乱序执行的 CPU 与优化过的编译器下,指令执行与内存访问可能与源代码顺序不一致,需通过内存序(memory order)来控制。
C++ 自 C++11 起定义了正式的跨平台内存模型,彻底解决了 "数据竞争" 问题。所有而非原子操作的并发读/写同一内存区都属于未定义行为。
二、std::atomic 基本用法(C++11--17)
1. 头文件与模板
cpp
#include <atomic>
// 常用特化
std::atomic<int> a_int;
std::atomic<bool> a_bool;
std::atomic<void*> a_ptr;
-
对内置整型、指针、bool 等都有专门的特化。
-
std::atomic 仅对"满足TriviallyCopyable"的类型有效。
2. Lock‐free 保证
-
a.is_lock_free():运行时查询该原子对象在当前平台是否无锁实现。
-
std::atomic::is_always_lock_free (constexpr,自 C++17):编译期常量,指示在所有平台上是否都无锁。
cpp
static_assert(std::atomic<int>::is_always_lock_free, "int should be lock-free");
3. 常用操作接口
操作 | 原型 | 说明 |
---|---|---|
store(v, mo) |
void store(T v, memory_order mo = memory_order_seq_cst) |
原子写 |
load(mo) |
T load(memory_order mo = memory_order_seq_cst) |
原子读 |
exchange(v, mo) |
T exchange(T v, memory_order mo = memory_order_seq_cst) |
交换并返回旧值 |
compare_exchange_weak/strong(expected, desired, mo...) |
bool compare_exchange_weak(T& expected, T desired, memory_order...); |
CAS 操作,失败时 expected 更新为实际值 |
fetch_add/sub/and/or/xor |
T fetch_add(T v, memory_order mo = ...) |
原子加减与位运算 |
atomic_thread_fence(mo) |
void atomic_thread_fence(memory_order mo) |
全局内存栅栏 |
注:所有接口可指定 memory_order,详见下节。 |
1. store / load
函数原型
cpp
void store(T desired,
std::memory_order mo = std::memory_order_seq_cst) noexcept;
T load(std::memory_order mo = std::memory_order_seq_cst) const noexcept;
语义
-
store:将原子对象的值写为 desired。
-
load:原子地读取当前值并返回。
默认内存序
- seq_cst:全局顺序,最严格。
示例
cpp
std::atomic<int> a{0};
// 写入
a.store(42); // 等价于 a.store(42, memory_order_seq_cst);
// 读取
int v = a.load(); // v == 42
注意
-
memory_order_relaxed 下,只保证原子性,不保证可见性或顺序。
-
memory_order_release 仅对 store 有效;memory_order_acquire 仅对 load 有效。
2. exchange
函数原型
cpp
T exchange(T desired,
std::memory_order mo = std::memory_order_seq_cst) noexcept;
语义
-
将原子对象的值替换为 desired,返回替换前的旧值。
-
原子地执行交换操作。
示例
cpp
std::atomic<int> a{5};
int old = a.exchange(10); // old == 5, a == 10
用途
- "取旧值并写新值" 场景,如实现简单的互斥标志。
3. compare_exchange_weak / compare_exchange_strong
函数原型
cpp
bool compare_exchange_weak(T& expected, T desired,
std::memory_order success = std::memory_order_seq_cst,
std::memory_order failure = std::memory_order_seq_cst) noexcept;
bool compare_exchange_strong(T& expected, T desired,
std::memory_order success = std::memory_order_seq_cst,
std::memory_order failure = std::memory_order_seq_cst) noexcept;
语义
- CAS(Compare‐And‐Swap):如果当前值等于 expected,则原子地写入 desired 并返回 true;否则将当前值载入 expected 并返回 false。
区别:
-
weak 可能 伪失败(spuriously fail)------常用于自旋循环,可提高性能。
-
strong 保证只有在值不匹配时才失败。
示例
cpp
std::atomic<int> a{0};
int expected = 0;
bool ok = a.compare_exchange_weak(expected, 1);
// 如果 a == 0,则写入 1 并 ok==true;
// 否则 expected 被赋为 a 的当前值,ok==false。
4. fetch_add / fetch_sub / fetch_and / fetch_or / fetch_xor
函数原型
cpp
T fetch_add(T arg,
std::memory_order mo = std::memory_order_seq_cst) noexcept;
T fetch_sub(T arg,
std::memory_order mo = std::memory_order_seq_cst) noexcept;
T fetch_and(T arg,
std::memory_order mo = std::memory_order_seq_cst) noexcept;
T fetch_or (T arg,
std::memory_order mo = std::memory_order_seq_cst) noexcept;
T fetch_xor(T arg,
std::memory_order mo = std::memory_order_seq_cst) noexcept;
语义
对当前值执行指定的算术或按位运算,返回 原子操作前 的旧值。
示例
cpp
复制
编辑
std::atomic<int> counter{0};
// 累加
int prev = counter.fetch_add(3); // prev==0, counter==3
// 按位或
std::atomic<int> flags{0b0011};
int oldf = flags.fetch_or(0b1100); // oldf==0b0011, flags==0b1111
5. atomic_thread_fence
函数原型
cpp
void atomic_thread_fence(std::memory_order mo) noexcept;
语义
-
在调用位置插入 全局内存栅栏。
-
保证在栅栏前后的非原子访问遵循指定的排序语义。
示例
cpp
int data = 0;
std::atomic<bool> ready{false};
// 写线程
data = 42;
atomic_thread_fence(std::memory_order_release);
ready.store(true, std::memory_order_relaxed);
// 读线程
if (ready.load(std::memory_order_relaxed)) {
atomic_thread_fence(std::memory_order_acquire);
// 这里一定能看到 data==42
}
6. std::atomic_flag 的专用操作
函数原型
cpp
std::atomic_flag flag = ATOMIC_FLAG_INIT;
bool test_and_set(std::memory_order mo = std::memory_order_seq_cst) noexcept;
void clear(std::memory_order mo = std::memory_order_seq_cst) noexcept;
语义
-
test_and_set:原子地将标志置1,返回先前值。
-
clear:将标志置0。
示例(自旋锁)
cpp
std::atomic_flag lock = ATOMIC_FLAG_INIT;
void acquire() {
while (lock.test_and_set(std::memory_order_acquire)) {
; // spin
}
}
void release() {
lock.clear(std::memory_order_release);
}
小结
-
选择接口:简洁的读写用 load/store;需要"交换"用 exchange;需要条件更新用 CAS;计数与标志位操作用 fetch_*;全局栅栏用 atomic_thread_fence;轻量锁用 atomic_flag。
-
内存序权衡:默认 seq_cst 最安全;对性能敏感时可以降级到 acquire/release 或 relaxed。
-
性能注意:
-
CAS 自旋要配合弱版(weak)使用,并写成循环;
-
避免在大对象上使用原子;
-
对简单标志推荐使用 atomic_flag。
-
四、C++17 中的改进点
-
is_always_lock_free 成为 constexpr
便于在编译期静态断言 lock‐free 保证。
-
std::atomic 的 constexpr 构造
从 C++17 起,原子对象可在常量表达式中初始化:
cpp
constexpr std::atomic<int> a{0};
- 内存模型与编译器优化的细微修正
标准更精确地界定了 consume 的含义(尽管大部分实现依然把它当成 acquire 来处理)。
五、C++20/23 的新特性
1. std::atomic_ref(C++20)
对已有对象提供原子视图,无需把原类型改为 std::atomic:
cpp
int counter = 0;
std::atomic_ref<int> a(counter);
a.fetch_add(1, std::memory_order_acq_rel);
// counter 也随之变化
- 语义与 std::atomic 基本一致,但不占用额外内存。
2. 原子等待/通知(C++20)
- wait/notify_one/notify_all:在整型或指针类型上直接阻塞与唤醒,替代条件变量的部分场景。
cpp
std::atomic<int> flag{0};
// 等待线程
if (flag.load() != 1)
flag.wait(0); // 阻塞直到不等于0
// 唤醒线程
flag.store(1);
flag.notify_one();
3. 并发构件(C++20/23,, , )
- 虽不属于原子模板,但与原子操作密切相关,提供更高层的同步原语。
4. 其他细节
-
在 C++23 中,std::atomic_flag::wait/notify_* 同步功能完善。
-
针对 shared_ptr、unique_ptr 的原子支持仍在并行标准/TS 中演进。
六、使用建议与注意事项
-
只在真正需要无锁同步时使用原子操作,不宜滥用;
-
对复杂同步场景,优先考虑 mutex+condition_variable 或并发算法库;
-
精确选择内存序:默认 seq_cst 最安全,对性能敏感时再切换到更松的语义;
-
警惕 memory_order_consume 在主流编译器上的支持不足。