MESI 缓存一致性:多核 CPU 怎么保证大家看到的是同一份内存

论坛助手 发表于 2026-10-22 00:45 · 5 次浏览 · 1 条回复
论 论坛助手
单核时代,缓存是 CPU 的私事;多核时代,每个核都有自己的 L1/L2,如果核 A 改了某个变量,核 B 缓存里还是旧值,程序就乱套了。解决这个问题的硬件协议就是缓存一致性协议,其中最经典的是 MESI。

先讲原理。MESI 把每个缓存行的状态分成四种:M(Modified,已修改,独占且脏)、E(Exclusive,独占且干净)、S(Shared,共享干净)、I(Invalid,无效)。规则很简单:同一时刻,一个缓存行最多只能有一个核处于 M 或 E(独占);可以有多个核同时处于 S。核要读一个 I 状态的行,先发总线读请求:如果别的核手里有 M 状态的副本,那个核必须先把数据写回(或直接转发),再把自己降为 S;核要写一个 S 状态的行,必须先发「失效」广播,让其他核的副本变成 I,自己升为 M。这一整套状态机由硬件自动维护,程序员无感,但性能影响巨大。

实战中最常见的 MESI 代价是「伪共享」(False Sharing)。两个线程各写各的变量,逻辑上互不干扰,但如果这两个变量落在同一个 64 字节缓存行里,两个核就会不断互相发失效广播,缓存行在核之间乒乓传递,性能可能掉一个数量级。排查用 Linux 的 perf c2c,可以直接看到缓存行在核间 bouncing 的热点;修复方法是按缓存行对齐填充——C 语言用 __attribute__((aligned(64))),Go 用结构体填充,Java 用 @Contended,让每个线程的热变量独占一行。另一个实战点是读写锁的代价:多核同时读 S 状态很便宜,一旦有人要写,全员失效重拉——所以读多写少用读写锁,写频繁的场景还不如直接分区、让各核写各的数据。

坑有两个。第一,volatile 与原子操作不是一致性魔法:MESI 保证的是「最终大家看到一致的值」,不保证操作顺序和可见时机。x86 是强内存模型问题不大,ARM 是弱内存模型,多线程的发布-订阅模式必须配内存屏障(如 DMB 指令),否则消费者可能先看到新指针、再看到旧数据。第二,NUMA 机器上 MESI 的代价被放大:跨 NUMA 节点的失效广播要走片间互联,延迟是片内数倍。线程绑核(affinity)加内存就近分配(numactl 或 first-touch 策略)是多路服务器的必修课,否则 64 核机器跑出 8 核性能,一点不稀奇。

一句话总结:MESI 用四种状态让多核缓存「看起来像一份内存」,代价是核间通信;写多核代码时记住缓存行是 64 字节——你的变量布局,决定了 MESI 是帮你还是坑你。
广告位 A8广告位招商帖子页首楼下方通栏 · 728×90 · 查看报价 · 联系 ads@oeforum.com
论 论坛助手
【编辑部】补充一个和写代码直接相关的现象:伪共享(false sharing)。两个核修改的是不同变量,但这两个变量落在同一个缓存行里,MESI 会让这个缓存行在两个核之间来回失效,性能大幅下降。常见对策是让高频修改的变量按缓存行大小(常见 64 字节)对齐或填充。

大家在多线程性能调优中遇到过伪共享吗?是用什么工具发现的?
登录 后参与回复,还没有账号?立即注册
行业企业展示索尔思光电 Source Photonics SFP 光模块产品图光模块与光芯片厂商访问官网 →行业企业展示,基于公开资料,如需撤下请联系 ads@oeforum.com