C++ 内存模型与原子操作详解
一、为什么需要内存模型?
现代硬件和编译器为了性能会对指令进行重排序(Reordering):
- 编译器重排:编译器在不改变单线程语义的前提下,可以任意重排指令顺序。
- CPU 乱序执行:CPU 可能以不同于程序顺序的方式执行指令(Out-of-Order Execution)。
- Store Buffer / 写缓冲区:CPU 的写操作不一定立即对其他核可见。
- Cache 一致性延迟:多核之间的缓存同步存在延迟。
这些优化在单线程下透明,但在多线程下会导致数据竞争(Data Race)和不可预期的行为。C++11 内存模型为此提供了一套跨平台的同步原语。
二、std::atomic 原子类型
std::atomic<T> 保证对类型 T 的操作是原子的(不可分割),不会被其他线程中途观察到中间状态。
1 2 3 4 5 6 7 8 9 10 11 12
| #include <atomic>
std::atomic<int> counter{0};
counter.fetch_add(1, std::memory_order_relaxed);
int val = counter.load(std::memory_order_acquire);
counter.store(42, std::memory_order_release);
|
注意:atomic 只保证操作本身的原子性,线程间的可见性和有序性由内存序(Memory Order)控制。
三、六种内存序(Memory Order)
C++ 提供了 6 种内存序,定义在 <atomic> 头文件中:
| 枚举值 |
含义 |
memory_order_relaxed |
无同步约束,仅保证原子性 |
memory_order_consume |
依赖链上的加载有序(已基本废弃,慎用) |
memory_order_acquire |
获取语义,防止后续读写被提前 |
memory_order_release |
释放语义,防止前面读写被延后 |
memory_order_acq_rel |
获取+释放,用于 RMW 操作 |
memory_order_seq_cst |
顺序一致,最强保证(默认) |
3.1 Relaxed — 仅保证原子性
1 2 3
| std::atomic<int> x{0}; x.store(1, std::memory_order_relaxed); int v = x.load(std::memory_order_relaxed);
|
- 不建立任何 happens-before 关系。
- 适用场景:计数器统计,不关心顺序,只关心最终值。
- 性能最高,但使用不当极易出 bug。
3.2 Release / Acquire — 释放-获取语义
这是最常用的同步对,构成线程间通信的基础。
规则:
store(..., release) 保证:该操作之前的所有写入,对随后执行 load(..., acquire) 并读到该值的线程可见。
load(..., acquire) 保证:该操作之后的所有读写,不会被重排到 load 之前。
1 2 3 4 5 6 7 8 9 10
| std::atomic<bool> ready{false}; int data = 0;
data = 42; ready.store(true, std::memory_order_release);
while (!ready.load(std::memory_order_acquire)); assert(data == 42);
|
内存屏障视角:
1 2 3
| 线程1: [写 data] → [store release] ↑ 同步点 线程2: [load acquire] → [读 data]
|
3.3 Acquire-Release (acq_rel) — 用于 RMW 操作
fetch_add、compare_exchange 等**读-改-写(Read-Modify-Write)**操作可以使用 acq_rel,同时具备 acquire 和 release 语义。
1 2 3 4 5 6 7 8 9
| std::atomic<int> lock{0};
while (lock.exchange(1, std::memory_order_acquire) == 1);
lock.store(0, std::memory_order_release);
|
3.4 Sequential Consistent — 顺序一致
1 2 3
| std::atomic<int> x{0}, y{0}; x.store(1, std::memory_order_seq_cst); y.store(1, std::memory_order_seq_cst);
|
- 所有
seq_cst 操作存在一个全局一致的全序(Total Order),所有线程观察到的操作顺序相同。
- 最容易推理,但开销最大(在 x86 上几乎无额外开销,在 ARM/POWER 上开销显著)。
- 默认的内存序,
atomic.store(val) 等价于 atomic.store(val, memory_order_seq_cst)。
四、Happens-Before 关系
Happens-Before 是 C++ 内存模型的核心概念,定义了操作间的可见性保证。
1
| A happens-before B → A 的效果对 B 可见
|
建立 happens-before 的方式:
- 同一线程内:按程序顺序,前面的操作 happens-before 后面的操作。
- Release-Acquire 配对:
store(release) synchronizes-with load(acquire)(读到该 store 的值),从而建立 happens-before。
- 互斥锁:
mutex.unlock() happens-before mutex.lock()(后续成功加锁)。
- 线程创建/join:父线程的创建操作 happens-before 子线程的起始;子线程的结束 happens-before
join() 返回。
五、CAS —— Compare-And-Swap
5.1 基本概念
CAS 是无锁编程的基石,语义为:
1 2 3 4 5 6 7
| if (*addr == expected) { *addr = desired; return true; } else { expected = *addr; // 更新 expected 为当前值 return false; }
|
C++ 提供两个版本:
1 2 3 4 5 6 7 8 9
| bool compare_exchange_strong(T& expected, T desired, std::memory_order success, std::memory_order failure);
bool compare_exchange_weak(T& expected, T desired, std::memory_order success, std::memory_order failure);
|
5.2 CAS 循环模式
1 2 3 4 5 6 7 8 9 10 11 12
| std::atomic<int> value{0};
int old_val = value.load(std::memory_order_relaxed); while (!value.compare_exchange_weak( old_val, old_val + 1, std::memory_order_release, std::memory_order_relaxed)) { }
|
5.3 success 与 failure 内存序
| 参数 |
含义 |
success |
CAS 成功时(执行了写入),使用的内存序 |
failure |
CAS 失败时(只做了读取),使用的内存序 |
规则: failure 的内存序不能强于 success,且 failure 不能是 release 或 acq_rel(因为失败时没有写入)。
5.4 ABA 问题
CAS 的经典陷阱:
1 2 3
| 线程1 读到 A 线程2 将 A → B → A(值变了又变回来) 线程1 CAS(A, new) ← 成功,但逻辑上数据已经被修改过了!
|
解决方案:
- 使用带版本号的 CAS(Tagged Pointer):将值和版本号打包在一个 64 位整数中。
- 使用
std::atomic<std::shared_ptr<T>> 等高层抽象。
- 在 GC 语言中天然避免(C++ 需要手动处理)。
1 2 3 4 5 6
| struct TaggedPtr { void* ptr; uintptr_t tag; }; std::atomic<TaggedPtr> head;
|
六、内存屏障(Memory Fence / Barrier)
除了原子操作自带的内存序,C++ 还提供独立的 fence:
1 2 3 4 5
| #include <atomic>
std::atomic_thread_fence(std::memory_order_release); std::atomic_thread_fence(std::memory_order_acquire); std::atomic_thread_fence(std::memory_order_seq_cst);
|
fence 比操作自带的内存序更”重”,会对 fence 前后的所有原子操作生效:
1 2 3 4
| data = 42; std::atomic_thread_fence(std::memory_order_release); flag.store(true, std::memory_order_relaxed);
|
七、常见无锁数据结构模式
7.1 无锁栈(Treiber Stack)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| template<typename T> class LockFreeStack { struct Node { T data; Node* next; }; std::atomic<Node*> head{nullptr};
public: void push(T val) { Node* node = new Node{val, nullptr}; node->next = head.load(std::memory_order_relaxed); while (!head.compare_exchange_weak(node->next, node, std::memory_order_release, std::memory_order_relaxed)); }
bool pop(T& out) { Node* old_head = head.load(std::memory_order_acquire); while (old_head) { if (head.compare_exchange_weak(old_head, old_head->next, std::memory_order_acquire, std::memory_order_relaxed)) { out = old_head->data; delete old_head; return true; } } return false; } };
|
7.2 双重检查锁(DCLP)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| std::atomic<Singleton*> instance{nullptr}; std::mutex mtx;
Singleton* get_instance() { Singleton* p = instance.load(std::memory_order_acquire); if (!p) { std::lock_guard<std::mutex> lock(mtx); p = instance.load(std::memory_order_relaxed); if (!p) { p = new Singleton(); instance.store(p, std::memory_order_release); } } return p; }
|
八、各内存序的硬件映射
| C++ 内存序 |
x86-64 指令 |
ARM 指令 |
relaxed load/store |
MOV |
LDR / STR |
acquire load |
MOV(x86 自带 acquire 语义) |
LDAR |
release store |
MOV(x86 自带 release 语义) |
STLR |
seq_cst store |
MFENCE + MOV / XCHG |
STLR + DMB |
seq_cst load |
MOV |
LDAR |
x86 的强内存模型:x86 硬件天然保证 store-load 之外的内存序,因此 acquire/release 在 x86 上几乎零开销。ARM 是弱内存模型,需要显式屏障指令。
九、总结与选型建议
1 2
| 性能高 ←————————————————————————→ 安全性高 relaxed consume acquire/release seq_cst
|
| 场景 |
推荐内存序 |
| 无顺序依赖的计数器(如统计) |
relaxed |
| 生产者-消费者、发布-订阅 |
release + acquire |
| 无锁队列、栈的 RMW |
acq_rel |
| 需要全局一致顺序(如 Dekker 算法) |
seq_cst |
| 不确定时 |
seq_cst(正确性优先) |
黄金法则:
- 优先使用高层同步原语(
mutex、condition_variable)。
- 需要无锁时,先用
seq_cst,确认正确后再优化内存序。
- 任何
relaxed 的使用都需要详细的注释说明为什么安全。
- 警惕 ABA 问题和内存回收问题(使用 Hazard Pointer 或 RCU)。# C++ 内存模型与原子操作详解
一、为什么需要内存模型?
现代硬件和编译器为了性能会对指令进行重排序(Reordering):
- 编译器重排:编译器在不改变单线程语义的前提下,可以任意重排指令顺序。
- CPU 乱序执行:CPU 可能以不同于程序顺序的方式执行指令(Out-of-Order Execution)。
- Store Buffer / 写缓冲区:CPU 的写操作不一定立即对其他核可见。
- Cache 一致性延迟:多核之间的缓存同步存在延迟。
这些优化在单线程下透明,但在多线程下会导致数据竞争(Data Race)和不可预期的行为。C++11 内存模型为此提供了一套跨平台的同步原语。
二、std::atomic 原子类型
std::atomic<T> 保证对类型 T 的操作是原子的(不可分割),不会被其他线程中途观察到中间状态。
1 2 3 4 5 6 7 8 9 10 11 12
| #include <atomic>
std::atomic<int> counter{0};
counter.fetch_add(1, std::memory_order_relaxed);
int val = counter.load(std::memory_order_acquire);
counter.store(42, std::memory_order_release);
|
注意:atomic 只保证操作本身的原子性,线程间的可见性和有序性由内存序(Memory Order)控制。
三、六种内存序(Memory Order)
C++ 提供了 6 种内存序,定义在 <atomic> 头文件中:
| 枚举值 |
含义 |
memory_order_relaxed |
无同步约束,仅保证原子性 |
memory_order_consume |
依赖链上的加载有序(已基本废弃,慎用) |
memory_order_acquire |
获取语义,防止后续读写被提前 |
memory_order_release |
释放语义,防止前面读写被延后 |
memory_order_acq_rel |
获取+释放,用于 RMW 操作 |
memory_order_seq_cst |
顺序一致,最强保证(默认) |
3.1 Relaxed — 仅保证原子性
1 2 3
| std::atomic<int> x{0}; x.store(1, std::memory_order_relaxed); int v = x.load(std::memory_order_relaxed);
|
- 不建立任何 happens-before 关系。
- 适用场景:计数器统计,不关心顺序,只关心最终值。
- 性能最高,但使用不当极易出 bug。
3.2 Release / Acquire — 释放-获取语义
这是最常用的同步对,构成线程间通信的基础。
规则:
store(..., release) 保证:该操作之前的所有写入,对随后执行 load(..., acquire) 并读到该值的线程可见。
load(..., acquire) 保证:该操作之后的所有读写,不会被重排到 load 之前。
1 2 3 4 5 6 7 8 9 10
| std::atomic<bool> ready{false}; int data = 0;
data = 42; ready.store(true, std::memory_order_release);
while (!ready.load(std::memory_order_acquire)); assert(data == 42);
|
内存屏障视角:
1 2 3
| 线程1: [写 data] → [store release] ↑ 同步点 线程2: [load acquire] → [读 data]
|
3.3 Acquire-Release (acq_rel) — 用于 RMW 操作
fetch_add、compare_exchange 等**读-改-写(Read-Modify-Write)**操作可以使用 acq_rel,同时具备 acquire 和 release 语义。
1 2 3 4 5 6 7 8 9
| std::atomic<int> lock{0};
while (lock.exchange(1, std::memory_order_acquire) == 1);
lock.store(0, std::memory_order_release);
|
3.4 Sequential Consistent — 顺序一致
1 2 3
| std::atomic<int> x{0}, y{0}; x.store(1, std::memory_order_seq_cst); y.store(1, std::memory_order_seq_cst);
|
- 所有
seq_cst 操作存在一个全局一致的全序(Total Order),所有线程观察到的操作顺序相同。
- 最容易推理,但开销最大(在 x86 上几乎无额外开销,在 ARM/POWER 上开销显著)。
- 默认的内存序,
atomic.store(val) 等价于 atomic.store(val, memory_order_seq_cst)。
四、Happens-Before 关系
Happens-Before 是 C++ 内存模型的核心概念,定义了操作间的可见性保证。
1
| A happens-before B → A 的效果对 B 可见
|
建立 happens-before 的方式:
- 同一线程内:按程序顺序,前面的操作 happens-before 后面的操作。
- Release-Acquire 配对:
store(release) synchronizes-with load(acquire)(读到该 store 的值),从而建立 happens-before。
- 互斥锁:
mutex.unlock() happens-before mutex.lock()(后续成功加锁)。
- 线程创建/join:父线程的创建操作 happens-before 子线程的起始;子线程的结束 happens-before
join() 返回。
五、CAS —— Compare-And-Swap
5.1 基本概念
CAS 是无锁编程的基石,语义为:
1 2 3 4 5 6 7
| if (*addr == expected) { *addr = desired; return true; } else { expected = *addr; // 更新 expected 为当前值 return false; }
|
C++ 提供两个版本:
1 2 3 4 5 6 7 8 9
| bool compare_exchange_strong(T& expected, T desired, std::memory_order success, std::memory_order failure);
bool compare_exchange_weak(T& expected, T desired, std::memory_order success, std::memory_order failure);
|
5.2 CAS 循环模式
1 2 3 4 5 6 7 8 9 10 11 12
| std::atomic<int> value{0};
int old_val = value.load(std::memory_order_relaxed); while (!value.compare_exchange_weak( old_val, old_val + 1, std::memory_order_release, std::memory_order_relaxed)) { }
|
5.3 success 与 failure 内存序
| 参数 |
含义 |
success |
CAS 成功时(执行了写入),使用的内存序 |
failure |
CAS 失败时(只做了读取),使用的内存序 |
规则: failure 的内存序不能强于 success,且 failure 不能是 release 或 acq_rel(因为失败时没有写入)。
5.4 ABA 问题
CAS 的经典陷阱:
1 2 3
| 线程1 读到 A 线程2 将 A → B → A(值变了又变回来) 线程1 CAS(A, new) ← 成功,但逻辑上数据已经被修改过了!
|
解决方案:
- 使用带版本号的 CAS(Tagged Pointer):将值和版本号打包在一个 64 位整数中。
- 使用
std::atomic<std::shared_ptr<T>> 等高层抽象。
- 在 GC 语言中天然避免(C++ 需要手动处理)。
1 2 3 4 5 6
| struct TaggedPtr { void* ptr; uintptr_t tag; }; std::atomic<TaggedPtr> head;
|
六、内存屏障(Memory Fence / Barrier)
除了原子操作自带的内存序,C++ 还提供独立的 fence:
1 2 3 4 5
| #include <atomic>
std::atomic_thread_fence(std::memory_order_release); std::atomic_thread_fence(std::memory_order_acquire); std::atomic_thread_fence(std::memory_order_seq_cst);
|
fence 比操作自带的内存序更”重”,会对 fence 前后的所有原子操作生效:
1 2 3 4
| data = 42; std::atomic_thread_fence(std::memory_order_release); flag.store(true, std::memory_order_relaxed);
|
七、常见无锁数据结构模式
7.1 无锁栈(Treiber Stack)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| template<typename T> class LockFreeStack { struct Node { T data; Node* next; }; std::atomic<Node*> head{nullptr};
public: void push(T val) { Node* node = new Node{val, nullptr}; node->next = head.load(std::memory_order_relaxed); while (!head.compare_exchange_weak(node->next, node, std::memory_order_release, std::memory_order_relaxed)); }
bool pop(T& out) { Node* old_head = head.load(std::memory_order_acquire); while (old_head) { if (head.compare_exchange_weak(old_head, old_head->next, std::memory_order_acquire, std::memory_order_relaxed)) { out = old_head->data; delete old_head; return true; } } return false; } };
|
7.2 双重检查锁(DCLP)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| std::atomic<Singleton*> instance{nullptr}; std::mutex mtx;
Singleton* get_instance() { Singleton* p = instance.load(std::memory_order_acquire); if (!p) { std::lock_guard<std::mutex> lock(mtx); p = instance.load(std::memory_order_relaxed); if (!p) { p = new Singleton(); instance.store(p, std::memory_order_release); } } return p; }
|
八、各内存序的硬件映射
| C++ 内存序 |
x86-64 指令 |
ARM 指令 |
relaxed load/store |
MOV |
LDR / STR |
acquire load |
MOV(x86 自带 acquire 语义) |
LDAR |
release store |
MOV(x86 自带 release 语义) |
STLR |
seq_cst store |
MFENCE + MOV / XCHG |
STLR + DMB |
seq_cst load |
MOV |
LDAR |
x86 的强内存模型:x86 硬件天然保证 store-load 之外的内存序,因此 acquire/release 在 x86 上几乎零开销。ARM 是弱内存模型,需要显式屏障指令。
九、总结与选型建议
1 2
| 性能高 ←————————————————————————→ 安全性高 relaxed consume acquire/release seq_cst
|
| 场景 |
推荐内存序 |
| 无顺序依赖的计数器(如统计) |
relaxed |
| 生产者-消费者、发布-订阅 |
release + acquire |
| 无锁队列、栈的 RMW |
acq_rel |
| 需要全局一致顺序(如 Dekker 算法) |
seq_cst |
| 不确定时 |
seq_cst(正确性优先) |
黄金法则:
- 优先使用高层同步原语(
mutex、condition_variable)。
- 需要无锁时,先用
seq_cst,确认正确后再优化内存序。
- 任何
relaxed 的使用都需要详细的注释说明为什么安全。
- 警惕 ABA 问题和内存回收问题(使用 Hazard Pointer 或 RCU)。
本作品由 lorixyu 于 2026-03-18 16:22:16 发布
除特别声明外,本站作品均采用
CC BY-NC-SA 4.0 许可协议,转载请注明来自
lorixyu