C++ 并发模型:内存序、可见性与指令重排
写在前面:当你第一次把
std::atomic、memory_order这些词读到手软时,可能会觉得这是 OS 或硬件工程师的专属领域。但其实理解内存模型并不需要掌握每一条 CPU 手册的汇编,只要抓住核心概念与工程实践,你就能写出既高效又安全的并发代码。
本文面向有一定 C++ 并发基础的读者(知道线程、互斥量、基本的 std::atomic 用法),但想把'为什么这样'弄清楚。我们会从 std::atomic 的语义出发,讲清 CPU cache coherence、内存屏障(fence)、指令重排 和 happens-before 的关系——不是空洞的定义,而是大量实战例子、容易踩的坑和调试技巧。文风尽量自然、通俗,像同事在白板前陪你聊通宵。
1. 为什么要理解内存模型?一个小实验
先给你一个看起来简单但会'出错'的例子:
int x = 0, y = 0;
void thread1() {
x = 1; // A
int r1 = y; // B
}
void thread2() {
y = 1; // C
int r2 = x; // D
}
直觉会告诉你 r1 == 0 && r2 == 0 不可能同时成立:因为若两个线程都先写后读,总有一个先写早于另一个后读。但在现实的多核处理器上,如果没有同步,两个读取同时得到 0 是可能的——因为写入对其他核可见需要时间,或编译器/CPU 做了重排。
这就是为什么我们不能把并发程序的正确性只交给直觉:你需要明确'一个操作对另一个操作是否可见'的约定,也就是happens-before。
2. 可见性、顺序与一致性:先把名词搞清楚
三个最常见的术语:
- 可见性(visibility):一个线程对某个内存写入何时能被另一个线程观察到。
- 顺序(ordering):在执行流中的操作顺序,分为程序顺序(程序编写的顺序)、一致顺序(在某种语义下保证的顺序)。
- 一致性(consistency):当多线程都观察到内存时,是否满足我们期待的全局一致性(例如线性一致性/顺序一致性)。
硬件保证的通常是缓存一致性(cache coherence)——同一地址的不同副本(存在于多个 cache 层)最终会保持一致。但这并不自动保证操作间的全局顺序性,也不防止编译器在不破坏单线程语义的前提下重排指令。
3. CPU 的缓存一致性(cache coherence)到底保不保底?
现代多核 CPU 通常实现 MESI(或其变体)协议来维护缓存一致性。
- MESI(Modified, Exclusive, Shared, Invalid)定义了缓存行在不同核心缓存间的状态转换,保证写入最终传播到其他核心。换句话说,CPU 层面把'同一地址不会无限分歧'这一事保证住了。


