好的,我们来系统、详细地介绍 硬件内存模型(Hardware Memory Model) 与 C/C++内存模型(C/C++ Memory Model),并分析它们的关系、区别和在多线程/并发编程中的应用。为了清晰,我会分成几个部分:概念、细节、示例和注意事项。
一、硬件内存模型(Hardware Memory Model)
1. 概念
硬件内存模型描述 CPU 与内存之间的可见性规则,即在多核系统中,内存访问的顺序是否被其他 CPU 核心看到,以及编译器、缓存等对访问顺序的影响。
核心问题是:在多核环境下,一个核心对内存的操作,其他核心何时能看到?
2. 常见硬件内存模型类型
- 强一致性模型(Sequential Consistency, SC)
- 定义:所有操作按程序顺序执行,所有线程看到的顺序相同。
- 特点:
- 每个操作立即对所有核心可见。
- 简单易理解,但性能差。
- 示例:单核 CPU 默认模型(没有乱序执行时)。
- 弱一致性模型(Weak Consistency)
- 定义:允许 CPU 对指令重排(reorder)、使用缓存和写缓冲区,使得程序顺序和实际内存访问顺序可能不同。
- 特点:
- 增加性能,但需要程序员使用屏障(fence)或同步操作。
- 常见于 x86、ARM、PowerPC 等现代 CPU。
- Total Store Order (TSO)
- x86 架构使用的内存模型。
- 特点:
- 写操作可能延迟对其他核心可见。
- 读操作一般不被乱序执行。
- 保持写-读、写-写顺序。
- Relaxed / Weak Ordering
- ARM 和 PowerPC 通常使用这种模型。
- 特点:
- 允许更多的乱序执行。
- 必须使用 memory barrier(如
dmb、dsb指令)保证顺序。
3. 硬件重排序
硬件可能做的优化:
- Store Buffering:写入延迟可见。
- Load Reordering:读取可能提前看到旧值。
- Write Coalescing:多次写入合并后才写入主存。
二、C/C++ 内存模型(C/C++ Memory Model)
C++11 引入了 标准化内存模型,让程序员在多线程下有定义良好的行为。C11 也类似。
1. 核心概念
- 顺序一致性(Sequentially Consistent)
- 默认
std::atomic操作是顺序一致的。 - 所有线程看到的操作顺序和程序顺序一致。
- 默认
- 内存顺序(Memory Order)
std::memory_order定义操作可见性:memory_order_relaxed:最弱,不保证顺序,只保证原子性。memory_order_acquire/memory_order_release:读-写同步。memory_order_acq_rel:组合读写。memory_order_seq_cst:顺序一致,默认最强。
- 原子类型(Atomic Types)
std::atomic<int>等保证单个变量操作的原子性和可见性。
- 数据竞争(Data Race)
- 多线程访问同一内存地址且至少有一个写操作,且未使用同步机制,则产生未定义行为(UB)。
2. 编译器优化
- 编译器可能 重新排序指令,但必须遵守 C++ 内存模型。
- 使用
volatile并不能保证多线程安全,它只禁止编译器优化访问,但不保证原子性或顺序。
三、硬件模型 vs C/C++ 内存模型
| 特性 | 硬件内存模型 | C/C++ 内存模型 |
|---|---|---|
| 描述 | CPU 对内存访问的可见性和顺序规则 | 编译器和 CPU 共同保证多线程行为 |
| 优化 | CPU 乱序、缓存、写缓冲 | 编译器乱序优化、内存序、原子操作 |
| 同步机制 | Memory barriers, fence 指令 | std::atomic, mutex, memory_order |
| 数据竞争 | CPU 无概念 | UB 定义明确,多线程安全由程序员控制 |
1. 关系
C/C++ 内存模型是 建立在硬件内存模型之上的抽象:
- C++ 定义了语言层的顺序一致性和原子性。
- 实现时,编译器会根据目标 CPU 内存模型插入适当的 屏障(fence)指令 来保证语义。
2. 示例
#include <atomic>
#include <thread>
#include <iostream>
std::atomic<int> x{0}, y{0};
int r1 = 0, r2 = 0;
void thread1() {
x.store(1, std::memory_order_relaxed);
r1 = y.load(std::memory_order_relaxed);
}
void thread2() {
y.store(1, std::memory_order_relaxed);
r2 = x.load(std::memory_order_relaxed);
}
int main() {
std::thread t1(thread1);
std::thread t2(thread2);
t1.join();
t2.join();
std::cout << r1 << " " << r2 << std::endl;
}
- 在 x86 上,可能出现
r1=0, r2=0的情况吗?- 可能,因为 store/load 使用
memory_order_relaxed,CPU 可以乱序执行。
- 可能,因为 store/load 使用
- 如果改为
memory_order_seq_cst:- 不可能出现
0 0,C++ 内存模型强制顺序一致性。
- 不可能出现
四、总结
- 硬件内存模型:
- 描述 CPU 对内存访问的可见性和顺序。
- 强调性能和乱序优化。
- C/C++ 内存模型:
- 描述程序中多线程访问内存的行为。
- 提供原子操作、内存顺序和同步机制。
- 关系:
- C/C++ 内存模型依赖硬件模型,但为程序员提供更高层次的抽象。
- 编译器将 C/C++ 内存顺序映射到 CPU fence/屏障指令上。
- 编程原则:
- 避免数据竞争。
- 明确使用
std::atomic或锁。 - 根据硬件特性选择合适的 memory_order,提高性能。
课程截图:

课程目录:
├─{1}–课程内容及相关说明
│ [1.1]–课程内容及相关说明.mp4
│
├─{2}–进程和线程
│ [2.1]–认识进程和线程.mp4
│ [2.2]–C语言对线程的支持.mp4
│ [2.3]–C语言编译器的选择.mp4
│ [2.4]–用C标准库函数创建线程.mp4
│ [2.5]–并行和并发.mp4
│
├─{3}–数据竞争
│ [3.1]–多线程数据竞争的例子.mp4
│ [3.2]–数据竞争是如何产生的.mp4
│
├─{4}–原子操作和锁
│ [4.1]–原子操作和原子变量.mp4
│ [4.2]–互斥锁.mp4
│ [4.3]–执行原子操作的机器指令.mp4
│ [4.4]–用机器指令实现原子操作的例子.mp4
│ [4.5]–用机器指令实现互斥锁的例子.mp4
│
├─{5}–线程通信及相关问题
│ [5.1]–一个线程间通信的例子.mp4
│ [5.2]–编译优化和指令重排.mp4
│ [5.3]–原子操作的神奇效应.mp4
│
├─{6}–流水线、乱序执行和缓存
│ 000.夸克手机注册领取1TB方法.png
│ [6.1]–什么是编排顺序.mp4
│ [6.2]–流水线.mp4
│ [6.3]–乱序执行.mp4
│ [6.4]–指令的执行和引退.mp4
│ [6.5]–存写缓冲器.mp4
│ [6.6]–缓存.mp4
│
├─{7}–硬件内存模型
│ [7.1]–多处理器系统和顺序一致性.mp4
│ [7.2]–顺序一致的执行.mp4
│ [7.3]–特别练习7.mp4
│ [7.4]–顺序一致性模型的访存次序.mp4
│ [7.5]–偏序和全序.mp4
│ [7.6]–顺序一致性存在单一全序.mp4
│ [7.7]–x86处理器的指令重排.mp4
│ [7.8]–x86处理器上的存全序TSO.mp4
│ [7.9]–x86的内存屏障指令MFENCE.mp4
│ [7.10]–x86-tso的访存次序(一).mp4
│ [7.11]–x86-tso的访存次序(二).mp4
│ [7.12]–x86-tso的访存次序(三).mp4
│ [7.13]–x86-tso的访存次序(四).mp4
│ [7.14]–x86-tso的访存次序(五).mp4
│ [7.15]–x86-tso的访存次序(六).mp4
│ [7.16]–x86访存次序的总结.mp4
│ [7.17]–多处理器和缓存一致性.mp4
│ [7.18]–MESI协议.mp4
│ [7.19]–MESI协议的状态转化.mp4
│ [7.20]–arm/power的访存次序(一).mp4
│ [7.21]–arm/power的访存次序(二).mp4
│ [7.22]–arm/power的访存次序(三).mp4
│ [7.23]–arm/power的访存次序(四).mp4
│ [7.24]–如何阻止指令重排.mp4
│ [7.25]–避免数据竞争的顺序一致性.mp4
│ [7.26]–x86平台上的同步操作指令.mp4
│ [7.27]–SC-DRF的实例.mp4
│ [7.28]–特别练习7-2.mp4
│
└─{8}–C/C++内存模型
[8.1]–C/C++内存模型简介.mp4
[8.2]–C语言的表达式.mp4
[8.3]–表达式的例子.mp4
[8.4]–表达式的功能.mp4
[8.5]–求值、值计算和副作用.mp4
[8.6]–前序、后序和序列点.mp4
[8.7]–特别练习8.mp4
[8.8]–无序和不确定顺序.mp4
[8.9]–冲突和数据竞争.mp4
[8.10]–原子操作库.mp4
[8.11]–一个多线程数据竞争的例子.mp4
[8.12]–用原子操作解决数据竞争的例子.mp4
[8.13]–C/C++内存模型的核心思想.mp4
[8.14]–原子操作的附加属性(同步和访存次序).mp4
[8.15]–通过原子操作施加指定的访存次序.mp4
[8.16]–通过原子操作施加内存同步.mp4
[8.17]–前发.mp4
[8.18]–同步操作及其分类.mp4
[8.19]–原子操作的线程间同步.mp4
[8.20]–依赖前序.mp4
[8.21]–线程间前发.mp4
[8.22]–可见副作用.mp4
[8.23]–再论前序和前发.mp4
[8.24]–松散的原子操作.mp4
[8.25]–原子变量的修改次序.mp4
[8.26]–原子操作的一致性规则.mp4
[8.27]–顺序一致性的原子操作.mp4
[8.28]–实例解析一.mp4
[8.29]–实例解析二.mp4
[8.30]–实例解析三.mp4
[8.31]–实例解析四.mp4
[8.32]–实例解析五.mp4
[8.33]–实例解析六.mp4
[8.34]–对原子操作函数的附加说明.mp4
[8.35]–C语言对原子类型的支持:存取的顺序一致性语义.mp4
[8.36]–C语言对原子类型的支持:复合赋值的顺序一致性语义.mp4
[8.37]–C语言对原子类型的支持:递增和递减的顺序一致性语义.mp4
[8.38]–C语言对原子类型的支持:不使用标准库函数的线程同步.mp4
