配套代码: DeguiLiu/newosp -- header-only C++17 嵌入式基础设施库
相关文章:
- 无锁编程核心原理: 从 CAS 到三种队列模式 -- acquire/release 内存序在无锁队列中的应用
- SPSC 无锁环形缓冲区设计剖析 -- ARM 指令映射、FakeTSO 单核优化
- 共享内存进程间通信 -- 跨进程场景的 ARM 内存序加固
- C++ 单例模式的线程安全实现 -- DCLP 失败的内存序根因
- 无锁异步日志设计 -- ARM 弱序模型下的日志缓冲区设计
CSDN 原文: C++多线程编程中的内存屏障/内存栅栏
1. 为什么 CPU 会重排序
多数内存屏障教程直接从 std::memory_order 六个枚举值讲起,告诉你 acquire 防止后面的读写提前、release 防止前面的读写延后。但为什么 CPU 要重排序?这个问题的答案藏在两个硬件组件里: Store Buffer 和 Invalidation Queue。
1.1 Store Buffer: 写操作的隐藏队列
现代 CPU 的 L1 Cache 访问延迟约 1-4 个时钟周期,但当写操作 cache miss 时,需要等待 MESI 协议完成 (获取 cache line 的独占权),延迟可达 几十到上百个周期。如果 CPU 每次写操作都阻塞等待缓存一致性完成,流水线将频繁停顿。
Store Buffer 的作用是让写操作立即完成: CPU 将写入值暂存到 Store Buffer,然后继续执行后续指令,无需等待缓存一致性协议完成。
CPU Core 0 CPU Core 1
┌──────────┐ ┌──────────┐
│ Pipeline │ │ Pipeline │
│ 执行 str │ │ 执行 ldr │
└────┬─────┘ └────┬─────┘
│ (1) 写入值暂存 │ (4) 从 L1 Cache 读取
┌────▼─────┐ ┌────▼─────┐
│ Store │ │ Invalidation│
│ Buffer │ (2) 异步刷新 ──────→ │ Queue │ (3) 延迟处理失效
└────┬─────┘ └────┬─────┘
│ │
┌────▼─────────────────────────────────────▼──────┐
│ L1 Cache / L2 Cache (MESI 协议) │
└─────────────────────────────────────────────────┘
关键问题: Core 0 的写入暂存在 Store Buffer 中,对 Core 1 不可见。Core 1 从自己的 L1 Cache 中读到的仍然是旧值。这不是 bug,而是 CPU 为了性能做出的设计决策。
Store Buffer 还引入了一个微妙的行为 -- Store Forwarding: 当 Core 0 读取自己刚写过的地址时,会直接从 Store Buffer 中获取最新值,绕过 L1 Cache。这意味着同一个 CPU 核心看到的写入顺序与其他核心看到的不同。
1.2 Invalidation Queue: 读操作的延迟
MESI 协议中,当一个核心要写入某条 cache line 时,需要向所有持有该 line 的其他核心发送 Invalidate 消息。接收方收到 Invalidate 后应该立即将对应 cache line 标记为 Invalid。
但如果接收方正在忙于其他操作 (流水线满载),立即处理 Invalidate 会导致停顿。因此硬件引入了 Invalidation Queue: 将收到的 Invalidate 消息排队,先回复 Acknowledge (让发送方继续),稍后再实际处理失效。
Core 1 收到 Invalidate(addr=0x1000):
┌──────────────────────────────┐
│ 1. 将 Invalidate 消息入队 │
│ 2. 立即回复 Ack (让 Core 0 │
│ 认为失效已完成) │
│ 3. 稍后处理: 将 cache line │
│ 标记为 Invalid │
└──────────────────────────────┘
在步骤 2 和 3 之间,Core 1 仍然
可以从自己的 L1 Cache 读到旧值!
关键问题: Core 1 已经回复了 Ack,Core 0 认为其他核心都已经看到了自己的写入,但 Core 1 实际上还在用旧的 cache line。这就是过期读 (stale read) 的硬件根源。
1.3 两个队列,两种乱序
Store Buffer 和 Invalidation Queue 分别导致了两种可见性问题:
| 硬件组件 | 导致的问题 | 影响 |
|---|---|---|
| Store Buffer | 写操作延迟对外可见 | 其他核心看不到最新写入 |
| Invalidation Queue | 读操作使用过期数据 | 本核心看到的是失效前的旧值 |
这两个机制的组合使得多核系统中,内存操作的执行顺序可能与程序顺序不同。这不是编译器优化,而是硬件行为 -- 即使你用 volatile 禁止编译器优化,CPU 仍然可能重排序。
2. MESI 协议: 缓存一致性不等于内存一致性
2.1 四种状态
MESI 协议是多核 CPU 维护缓存一致性的标准协议。每条 cache line 有四种状态:
| 状态 | 含义 | 可读 | 可写 | 其他核心状态 |
|---|---|---|---|---|
| M (Modified) | 已修改,与内存不一致 | 是 | 是 | 无 (独占) |
| E (Exclusive) | 独占,与内存一致 | 是 | 是 (→M) | 无 (独占) |
| S (Shared) | 共享,与内存一致 | 是 | 否 (需先 Invalidate) | 多核共享 |
| I (Invalid) | 无效 | 否 | 否 | - |
2.2 一致性 vs 一致性
MESI 保证的是 Cache Coherence (缓存一致性): 对同一地址的所有写入,所有核心最终看到相同的值和相同的顺序。但它不保证 Memory Consistency (内存一致性): 不同地址的写入顺序在不同核心上的可见顺序可能不同。
// 初始: x = 0, y = 0
// Core 0 // Core 1
x = 1; // (1) y = 1; // (3)
r1 = y; // (2) r2 = x; // (4)
// 可能的结果: r1 == 0 && r2 == 0
这个经典的 Store Buffer Litmus Test 在 ARM 上可以真实发生:
- Core 0 执行 (1): x=1 进入 Core 0 的 Store Buffer (对 Core 1 不可见)
- Core 1 执行 (3): y=1 进入 Core 1 的 Store Buffer (对 Core 0 不可见)
- Core 0 执行 (2): 读 y,从 L1 Cache 读到旧值 0
- Core 1 执行 (4): 读 x,从 L1 Cache 读到旧值 0
MESI 保证 x 最终为 1、y 最终为 1,但不保证 Core 0 在写 x 后立即看到 Core 1 写的 y。
这就是内存屏障的存在意义: 强制 Store Buffer 刷新或 Invalidation Queue 处理,使特定的内存操作按程序顺序对其他核心可见。
3. 四种屏障类型
根据 Store Buffer 和 Invalidation Queue 的组合,内存屏障被分为四种基本类型:
3.1 StoreStore Barrier
Store A
--- StoreStore ---
Store B
保证: Store A 在 Store B 之前刷出 Store Buffer,即其他核心先看到 A 的写入,再看到 B 的写入。
硬件机制: 标记 Store Buffer 中的当前条目,后续的 Store 必须等待这些条目刷新到缓存后才能继续。
典型场景: 生产者写数据,然后写 flag 通知消费者。StoreStore 保证消费者看到 flag=1 时,数据已经可见。
// 生产者 (Core 0)
data = 42; // Store A
// --- StoreStore ---
flag = 1; // Store B
// 消费者 (Core 1)
while (flag != 1); // 看到 flag=1 时
use(data); // data 保证是 42
3.2 LoadLoad Barrier
Load A
--- LoadLoad ---
Load B
保证: Load A 完成后再执行 Load B,Load B 不会使用比 Load A 更旧的数据。
硬件机制: 处理 Invalidation Queue 中的所有待处理失效消息,确保后续 Load 读到最新值。
典型场景: 消费者先读 flag,再读 data。LoadLoad 保证读到 flag=1 后,读 data 不会命中过期的 cache line。
3.3 LoadStore Barrier
Load A
--- LoadStore ---
Store B
保证: Load A 完成后再执行 Store B。防止 Store 被提前到 Load 之前执行。
使用较少: x86 TSO 天然禁止 LoadStore 重排,ARM 弱序需要。
3.4 StoreLoad Barrier (Full Fence)
Store A
--- StoreLoad ---
Load B
保证: Store A 对所有核心可见后再执行 Load B。这是最强也是最昂贵的屏障。
硬件机制: 刷新 Store Buffer 中的所有条目 + 处理 Invalidation Queue 中的所有消息。相当于 StoreStore + LoadLoad + LoadStore 的组合。
为什么最贵: Store Buffer 刷新需要等待 MESI 协议完成 (可能需要等待远端核心的 Ack),这个延迟通常是几十到上百个时钟周期。
3.5 四种屏障与硬件机制的映射
| 屏障类型 | 作用于 | 硬件效果 |
|---|---|---|
| StoreStore | Store Buffer | 刷新已有条目后才允许新 Store |
| LoadLoad | Invalidation Queue | 处理待失效消息后才允许新 Load |
| LoadStore | 两者 | Load 完成后才允许 Store 提交 |
| StoreLoad | 两者 | 完全刷新 Store Buffer + Invalidation Queue |
4. 三个层次: 编译器屏障、硬件屏障、C++ memory_order
代码到硬件之间有三层可能的重排序,需要三个层次的屏障:
源代码
│
┌─────▼─────┐
│ 编译器优化 │ ← 编译器屏障 (阻止编译器重排)
└─────┬─────┘
│
┌─────▼─────┐
│ CPU 乱序 │ ← 硬件屏障 (阻止 CPU 重排)
│ 执行引擎 │
└─────┬─────┘
│
┌─────▼─────┐
│ Store Buffer│ ← 内存屏障 (强制刷新/排空)
│ Inv. Queue │
└─────┬─────┘
│
内存
4.1 编译器屏障
编译器屏障只阻止编译器重排序指令,不生成任何硬件屏障指令。
// GCC/Clang 内联汇编编译器屏障
asm volatile("" ::: "memory");
// C++11 标准方式
std::atomic_signal_fence(std::memory_order_acq_rel);
asm volatile("" ::: "memory") 的含义:
"": 空指令 (不生成机器码)volatile: 不被编译器消除"memory": 告诉编译器内存已被修改,不要跨越此点重排内存操作
适用场景: 单核 MCU (无 CPU 乱序执行问题),或仅需要阻止编译器优化时。atomic_signal_fence 用于同一线程中信号处理函数与主线程的同步。
// newosp SPSC 在 FakeTSO 单核模式下:
// 只用编译器屏障替代硬件屏障 (零硬件开销)
#ifdef OSP_FAKE_TSO
std::atomic_signal_fence(std::memory_order_release); // 编译器屏障
#else
std::atomic_thread_fence(std::memory_order_release); // 硬件屏障
#endif
详见 SPSC 无锁环形缓冲区设计剖析 中的 FakeTSO 机制。
4.2 硬件屏障
硬件屏障生成实际的 CPU 屏障指令,阻止 CPU 乱序执行和 Store Buffer/Invalidation Queue 的延迟效应。
// C++11 标准方式 (独立屏障)
std::atomic_thread_fence(std::memory_order_acquire); // LoadLoad + LoadStore
std::atomic_thread_fence(std::memory_order_release); // LoadStore + StoreStore
std::atomic_thread_fence(std::memory_order_acq_rel); // 以上全部
std::atomic_thread_fence(std::memory_order_seq_cst); // Full fence (含 StoreLoad)
在 ARM 上的编译结果:
; atomic_thread_fence(acquire) → dmb ishld (Load barrier)
; atomic_thread_fence(release) → dmb ish (Full barrier)
; atomic_thread_fence(seq_cst) → dmb ish (Full barrier)
注意: ARM 没有单独的 StoreStore 屏障指令,release 和 seq_cst 都映射到 dmb ish (完全数据屏障)。这意味着 ARM 上 release fence 的开销与 seq_cst fence 相同。
4.3 C++ memory_order: 绑定到原子操作的屏障
C++11 定义了六种 memory_order,它们不是独立屏障,而是附加在原子操作上的排序约束:
| memory_order | 语义 | 组合的屏障效果 |
|---|---|---|
relaxed | 仅保证原子性 | 无屏障 |
consume | 数据依赖序 (deprecated) | 理论上比 acquire 弱 |
acquire | 后续读写不提前到此 load 之前 | LoadLoad + LoadStore |
release | 前面读写不延后到此 store 之后 | LoadStore + StoreStore |
acq_rel | acquire + release | LoadLoad + LoadStore + StoreStore |
seq_cst | 全序 (所有线程看到相同顺序) | Full fence (含 StoreLoad) |
独立屏障 vs 原子操作上的 memory_order:
// 方式一: 独立屏障 (atomic_thread_fence)
data.store(42, std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_release);
flag.store(1, std::memory_order_relaxed);
// 方式二: 原子操作附加 memory_order (更常用)
data.store(42, std::memory_order_relaxed);
flag.store(1, std::memory_order_release); // release 语义附加在 flag 的 store 上
两种方式在 ARM 上生成的指令几乎相同,但方式二更简洁。C++ 标准推荐在原子操作上直接指定 memory_order,仅在需要与非原子操作建立 happens-before 关系时使用独立屏障。
4.4 三个层次总结
| 层次 | 机制 | 阻止的重排序 | 硬件开销 |
|---|---|---|---|
| 编译器屏障 | asm volatile("" ::: "memory") / atomic_signal_fence | 仅编译器重排 | 零 (无机器指令) |
| 硬件屏障 | atomic_thread_fence / 内联汇编 | 编译器 + CPU 重排 | DMB/DSB 指令 (几十周期) |
| 原子操作 memory_order | atomic.store/load(order) | 编译器 + CPU 重排 (绑定到特定操作) | 取决于 order 级别 |
5. x86 vs ARM: 强序与弱序
5.1 x86 TSO (Total Store Order)
x86 实现了 TSO (Total Store Order) 模型,这是一种接近顺序一致性的强序模型:
- 每个核心的 Store 按程序顺序对所有核心可见 (StoreStore 天然保证)
- 每个核心的 Load 按程序顺序执行 (LoadLoad 天然保证)
- Load 不会被重排到 Store 之后 (LoadStore 天然保证)
- 唯一允许的重排: Store 可以被后续的 Load 越过 (StoreLoad 可重排)
x86 允许的重排序:
Store A → Load B 可能变成 Load B → Store A (StoreLoad 重排)
x86 禁止的重排序:
Store A → Store B (StoreStore ✓ 保序)
Load A → Load B (LoadLoad ✓ 保序)
Load A → Store B (LoadStore ✓ 保序)
因此,x86 上大部分 acquire 和 release 操作不需要生成屏障指令 -- 硬件已经提供了足够的保证。只有 seq_cst 的 store 操作需要额外的 MFENCE 或 XCHG 指令来阻止 StoreLoad 重排。
这也是为什么很多并发 bug 在 x86 上不会复现,却在 ARM 上崩溃 -- x86 的 TSO 掩盖了缺失的内存屏障。
5.2 ARM 弱序模型
ARM 实现了 弱序 (Weakly Ordered) 内存模型,四种重排序都可能发生:
ARM 允许的重排序:
Store → Store (StoreStore 可重排) ✗
Load → Load (LoadLoad 可重排) ✗
Load → Store (LoadStore 可重排) ✗
Store → Load (StoreLoad 可重排) ✗
x86 上只有最后一种可重排
这意味着 ARM 上每个需要排序保证的操作都必须显式插入屏障指令。编译器在生成 ARM 代码时,会根据 memory_order 自动插入必要的 DMB 指令。
5.3 C++ memory_order 在两种架构上的代价
| memory_order | x86 (TSO) | ARM (弱序) |
|---|---|---|
relaxed | 无额外指令 | 无额外指令 |
acquire (load) | 无额外指令 | dmb ishld (或 ldar) |
release (store) | 无额外指令 | dmb ish (或 stlr) |
seq_cst (store) | MFENCE 或 XCHG | dmb ish |
seq_cst (load) | 无额外指令 | ldar + dmb ish |
嵌入式实践: 在 ARM 上,acquire/release 与 seq_cst 的实际开销差异取决于具体微架构,但原则是用最弱的足够的 memory_order。relaxed 最快 (零开销),seq_cst 最慢 (完全屏障)。
// 嵌入式 SPSC: 只需要 acquire/release (不需要 seq_cst)
// 生产者
buffer[head] = data;
head_.store(new_head, std::memory_order_release); // ARM: stlr 或 dmb ish + str
// 消费者
auto h = head_.load(std::memory_order_acquire); // ARM: ldar 或 ldr + dmb ishld
auto data = buffer[h];
详见 SPSC 无锁环形缓冲区设计剖析 和 共享内存进程间通信 中的 ARM 内存序实战分析。
6. ARM 三条屏障指令
ARM 提供三条屏障指令,语义精确且不可互换:
6.1 DMB (Data Memory Barrier)
DMB ISH ; 全屏障: 在此之前的所有内存访问完成后, 才允许之后的内存访问
DMB ISHLD ; Load 屏障: 在此之前的所有 Load 完成后, 才允许之后的 Load/Store
DMB ISHST ; Store 屏障: 在此之前的所有 Store 完成后, 才允许之后的 Store
语义: 确保 DMB 之前和之后的数据内存访问的顺序性。DMB 只影响内存访问指令 (Load/Store),不影响其他指令的执行。
后缀含义:
ISH(Inner Shareable): 作用于内部共享域 (通常是所有 CPU 核心)OSH(Outer Shareable): 作用于外部共享域 (含 DMA 控制器等)SY(System): 作用于整个系统LD: 仅限 Load 操作ST: 仅限 Store 操作
C++ 映射:
memory_order_acquire → DMB ISHLD (Load 屏障)
memory_order_release → DMB ISH (Full 屏障, ARM 无单独 StoreStore)
memory_order_seq_cst → DMB ISH (Full 屏障)
代价: DMB 的延迟在 ARM Cortex-A 系列上通常为 20-60 个时钟周期 (取决于 Store Buffer 深度和缓存一致性延迟)。
6.2 DSB (Data Synchronization Barrier)
DSB ISH ; 等待之前的所有内存访问完成, 且对其他核心可见
DSB ISHST ; 等待之前的所有 Store 完成并对其他核心可见
语义: DSB 比 DMB 更强。DMB 只保证顺序性 (A 在 B 之前完成),DSB 保证完成性 (A 已经完成,所有核心都已看到结果)。DSB 之后的任何指令 (不仅是内存访问) 都不会执行,直到 DSB 之前的所有内存访问完成。
DMB vs DSB 的区别:
DMB: "屏障之前的内存操作先于屏障之后的内存操作"
DSB: "屏障之前的内存操作全部完成后,才执行屏障之后的任何指令"
| 特性 | DMB | DSB |
|---|---|---|
| 保证内存操作顺序 | 是 | 是 |
| 等待内存操作完成 | 否 | 是 |
| 阻塞后续非内存指令 | 否 | 是 |
| 典型延迟 | 20-60 周期 | 更高 (需等待写入对所有核心可见) |
适用场景: 修改页表、修改 MMU 配置、DMA 操作完成确认等需要确保写入已完成 (而非仅已排序) 的场景。
// 修改页表后必须用 DSB (而非 DMB)
modify_page_table_entry();
asm volatile("dsb ish" ::: "memory"); // 等待页表修改对所有核心可见
asm volatile("isb" ::: "memory"); // 刷新指令流水线
6.3 ISB (Instruction Synchronization Barrier)
ISB SY ; 刷新指令流水线
语义: 刷新 CPU 的指令流水线和预取缓冲。ISB 之后获取的所有指令都从缓存或内存中重新获取,确保 ISB 之前的系统寄存器修改 (如 MMU 配置、中断控制器配置) 对后续指令可见。
ISB 与 DMB/DSB 的本质区别: DMB 和 DSB 作用于数据流 (Load/Store),ISB 作用于指令流 (指令预取和解码)。
适用场景:
- 修改 SCTLR (系统控制寄存器) 后刷新流水线
- 使能/关闭 MMU 后
- 修改中断向量表后
- 自修改代码 (Self-Modifying Code) 后
// 自修改代码: 修改内存中的指令后
write_new_instruction(addr);
asm volatile("dsb ish" ::: "memory"); // 确保新指令写入完成
asm volatile("isb" ::: "memory"); // 刷新流水线, 使新指令生效
6.4 选择指南
需要保证数据访问顺序? → DMB (最轻量, C++ atomic 首选)
需要确保数据写入已完成? → DSB (页表/DMA/MMIO 场景)
需要刷新指令流水线? → ISB (系统寄存器/自修改代码)
嵌入式开发中的使用频率: C++ std::atomic 和 atomic_thread_fence 只会生成 DMB,不会生成 DSB 或 ISB。后两者是系统级编程 (内核、Bootloader、BSP) 的工具,应用层代码几乎不会直接使用。
7. 实战: 内存屏障如何保护无锁数据结构
7.1 生产者-消费者 (Acquire-Release)
这是最常见的内存屏障使用模式,也是 SPSC 环形缓冲区的核心:
// 生产者 (写数据 → release store flag)
void produce(T data) {
buffer[slot] = data; // (1) 普通 Store
head.store(new_head, std::memory_order_release); // (2) Release Store
}
// 消费者 (acquire load flag → 读数据)
bool consume(T& out) {
auto h = head.load(std::memory_order_acquire); // (3) Acquire Load
if (h == tail) return false;
out = buffer[tail]; // (4) 普通 Load
return true;
}
ARM 生成的指令:
; 生产者
str r1, [buffer, slot] ; (1) 普通 Store: data
dmb ish ; release fence
str r2, [head] ; (2) Store: new_head
; 消费者
ldr r3, [head] ; (3) Load: head
dmb ishld ; acquire fence
ldr r4, [buffer, tail] ; (4) 普通 Load: data
dmb ish 保证 (1) 在 (2) 之前对消费者可见; dmb ishld 保证 (3) 在 (4) 之前完成。两者配合,消费者看到 head 更新时,data 一定已写入。
7.2 DCLP (Double-Checked Locking Pattern)
C++ 单例模式的线程安全实现 详细分析了 DCLP 在 C++03 中失败的原因。其核心就是缺少内存屏障:
// C++11 正确的 DCLP
Singleton* Singleton::getInstance() {
auto* p = instance.load(std::memory_order_acquire); // (1) acquire
if (!p) {
std::lock_guard<std::mutex> lock(mtx);
p = instance.load(std::memory_order_relaxed); // (2) 在锁内 relaxed 即可
if (!p) {
p = new Singleton();
instance.store(p, std::memory_order_release); // (3) release
}
}
return p;
}
(3) 的 release 保证 new Singleton() 的所有构造操作在指针发布之前完成; (1) 的 acquire 保证其他线程通过指针访问对象时,看到完全构造的状态。
7.3 newosp FakeTSO: 单核优化
在单核 MCU (Cortex-M 系列) 上,没有多核缓存一致性问题,硬件屏障指令是纯粹的浪费。newosp 的 SPSC 环形缓冲区提供了 FakeTSO 编译选项:
// 单核模式: 所有 atomic_thread_fence 降级为 atomic_signal_fence
// 效果: 零硬件屏障指令, 仅阻止编译器重排序
#define OSP_FAKE_TSO 1
这是编译器屏障与硬件屏障的典型工程权衡:
- 多核 ARM: 必须用
atomic_thread_fence→ 生成DMB - 单核 MCU: 只需
atomic_signal_fence→ 零硬件开销
详见 SPSC 无锁环形缓冲区设计剖析 中的 FakeTSO 分析。
8. 常见误区
误区一: volatile = 内存屏障
volatile 只阻止编译器对该变量的优化 (不消除读写、不合并、不重排同一 volatile 变量的操作)。它不阻止:
- 编译器对 volatile 和非 volatile 操作之间的重排
- CPU 的乱序执行和 Store Buffer 延迟
volatile int flag = 0;
int data = 0;
// 线程 A
data = 42; // 编译器可能将这条重排到 flag=1 之后
flag = 1; // volatile 不保护 data 和 flag 之间的顺序
// 正确做法: 用 atomic + memory_order
std::atomic<int> flag{0};
data = 42;
flag.store(1, std::memory_order_release); // data 写入在 flag 之前可见
误区二: seq_cst 最安全,应该到处用
seq_cst 是最强的排序保证,但也是最昂贵的。在 ARM 上,每个 seq_cst 操作都会生成 DMB 指令。如果代码中有大量 atomic 操作 (如无锁队列的 CAS 循环),过度使用 seq_cst 会显著降低性能。
原则: 分析数据流向,使用最弱的足够的 memory_order:
- 单纯的计数器/统计:
relaxed - 生产者-消费者:
acquire/release - 需要全序 (如 Dekker 互斥算法):
seq_cst
误区三: 在 x86 上测试通过 = 内存序正确
x86 的 TSO 模型天然保证了大部分排序 (只有 StoreLoad 可重排)。一段代码在 x86 上跑了一年没出问题,移植到 ARM 后可能立即出现数据竞争。
实践建议: 使用 ThreadSanitizer (-fsanitize=thread) 检测数据竞争,不依赖特定架构的内存模型。
参考资料
- C++多线程编程中的内存屏障/内存栅栏 -- 本文的 CSDN 原始版本
- Memory Barriers: a Hardware View for Software Hackers -- Paul McKenney 经典论文
- A Tutorial Introduction to the ARM and POWER Relaxed Memory Models -- ARM 弱序模型形式化教程
- Herb Sutter: atomic<> Weapons -- C++ 内存模型与硬件
- Jeff Preshing: Memory Barriers Are Like Source Control Operations -- 四种屏障类型的直觉解释
- ARM Architecture Reference Manual -- DMB/DSB/ISB 官方规范
- newosp GitHub 仓库 -- SPSC/MPSC 无锁实现中的内存序实战