内存屏障是什么?一条指令管住"重排"
答案:内存屏障是一条特殊指令,阻止编译器和 CPU 对屏障两侧的内存操作做重排,保证多核/多线程下的可见性与执行顺序。四种重排各有对应屏障。
为什么需要(30 秒理解)
// 线程 A
data = 1; // 普通写
flag = 1; // 假设是原子写
// 线程 B
while (!flag) ; // 等 flag
use(data); // 期望读到 1没有屏障时:编译器和 CPU 可能把 data=1 挪到 flag=1 之后执行(写写重排),或线程 B 读 flag=1 后仍读到旧 data。加 smp_wmb()/smp_store_release() 就能保证"data 先于 flag 可见"。
四种重排与对应屏障
| 重排类型 | 含义 | x86 屏障 | C++ memory_order |
|---|---|---|---|
| LoadLoad | 读被重排到读前 | lfence/smp_rmb | acquire |
| StoreStore | 写被重排到写前 | sfence/smp_wmb | release |
| LoadStore | 读被重排到写前 | 部分硬件 | acquire+release |
| StoreLoad | 写被重排到读前 | mfence/smp_mb | seq_cst |
注意:x86/ARM 等主流 CPU 的硬件重排较弱(TSO 模型下 x86 只允许 StoreLoad 重排),真正"重排多"的是编译器优化——所以屏障通常要同时作用于编译器和 CPU 两层。
实际怎么用(按语言)
- Linux 内核:
smp_mb()、smp_rmb()、smp_wmb();更推荐smp_store_release()/smp_load_acquire(); - C++:
std::atomic+memory_order_acquire/release/acq_rel/seq_cst; - Go:
sync/atomic的 Load/Store 自带顺序保证,别手写屏障。
深度入口
- 完整原理与实验:内存重排与内存屏障
- Store Buffer 与失效队列:Store Buffer 详解
- 原子操作基础:原子操作
FAQ
Q: 内存屏障是什么? A: 内存屏障是一条特殊指令,告诉编译器和 CPU:"屏障两侧的内存操作不许重排",保证多线程/多核场景下的可见性与顺序。
Q: 为什么需要内存屏障? A: 编译器和 CPU 为了性能会重排内存操作,单线程无害,但多线程下可能导致"先写的后生效、后读的读到旧值",破坏锁之外的自定义同步逻辑。
Q: 有哪几种内存重排,分别对应什么屏障? A: 四种:LoadLoad(读读重排)、StoreStore(写写重排)、LoadStore(读写重排)、StoreLoad(写读重排)。对应屏障:lfence 防读读、sfence 防写写、mfence 全防。
Q: Linux 里怎么用内存屏障? A: 常用宏:smp_mb() 全屏障、smp_rmb() 读屏障、smp_wmb() 写屏障;还有 smp_store_release()/smp_load_acquire() 这类"单向屏障"更常用。
Q: C++ 里怎么表达内存屏障? A: 用 std::atomic 的 memory_order:memory_order_acquire、memory_order_release、memory_order_acq_rel、memory_order_seq_cst 对应不同屏障强度。
Q: volatile 能当内存屏障用吗? A: 不能。volatile 只阻止编译器优化该变量读写,不能阻止 CPU 重排,也不保证原子性。正确做法是用原子操作 + memory order。
一句话总结:内存屏障 = 阻止编译器和 CPU 重排内存操作;x86 硬件重排少但编译器重排多,所以按语言用原子操作的 acquire/release 比手写屏障更安全省事。