Skip to main content

MESI

📅 2026-03-27 ✏️ 2026-03-27 CS
No related notes

1 · MESI#

https://en.wikipedia.org/wiki/MESI_protocol

MESI 是多核 CPU 中最经典的 cache coherence protocol(缓存一致性协议) 之一,用来保证不同核心看到的同一块内存不会各说各话。

1.1 · S 什么场景#

现代 CPU 每个核心都有自己的 L1/L2 cache。这样读写很快,但也带来一个根本问题:

  • 核心 A 读过变量 x,把 cache line 缓存在本地
  • 核心 B 也读过 x,也缓存了一份
  • 如果 B 后来修改了 x
  • A 本地 cache 里还是旧值

只要多个核心会同时缓存同一个 cache line,就必须回答:

  • 谁可以写?
  • 其他核心的旧副本什么时候失效?
  • 数据什么时候写回内存?

MESI 就是在这个场景下出现的。

1.2 · C 什么冲突#

冲突在于:缓存的本地性共享数据的一致性 天然对立。

  • 想要性能,就希望每个核心把数据留在自己 cache 里,少访问主存
  • 想要正确性,就要求所有核心对同一地址的观察结果一致

如果没有一致性协议,会出现:

  • 一个核心写了数据,另一个核心继续读旧值
  • 多个核心同时认为自己可以写同一 cache line
  • 内存、核心 A cache、核心 B cache 三者状态不一致

所以协议必须在“尽量少同步”和“绝不能读脏旧数据”之间找平衡。

1.3 · Q 什么问题#

1.3.1 · 1. MESI 四个状态分别是什么?#

MESI 是四种 cache line 状态的首字母:

  • M = Modified 这行数据只在当前核心 cache 中,且被修改过,和主存不一致;将来被替换时需要写回
  • E = Exclusive 这行数据只在当前核心 cache 中,但没有被修改,和主存一致;当前核心后续写它时,可以直接升级为 M
  • S = Shared 这行数据可能同时存在于多个核心 cache 中,内容和主存一致;读可以,写之前必须先让别人的副本失效
  • I = Invalid 本地这行 cache line 无效,不能使用;需要重新从别处获取

一个很实用的记忆方式:

  • M/E:当前核心“独占”
  • S:多个核心“共享只读”
  • I:没有这份有效数据

1.3.2 · 2. 它到底解决的是什么一致性问题?#

它解决的是 cache coherence,不是更高层的 memory consistency model

  • coherence 关心:同一个地址 的值,在多个 cache 之间怎么保持一致
  • consistency 关心:多个地址 的读写,在程序语义上允许怎样重排

所以 MESI 回答的是:

当多个核心缓存了同一个 cache line 时,如何保证写入后其他核心不会继续使用过期副本?

1.3.3 · 3. 典型的读写流程怎么走?#

假设一开始所有核心对某个 cache line 都是 I

1.3.3.1 · 场景 1:核心 A 读#

  • A 发生 cache miss,发出读请求
  • 如果别的核心没有这行,A 拿到数据后通常进入 E
  • 如果别的核心也有这行,A 通常进入 S

1.3.3.2 · 场景 2:核心 A 在 E 上写#

  • 因为它本来就独占,且和别人不共享
  • 可以直接从 E -> M
  • 不需要先通知别人失效

这也是 E 状态存在的意义:优化“先读后写”的常见路径。

1.3.3.3 · 场景 3:核心 A 在 S 上写#

  • A 必须先发出失效请求(invalidate)
  • 其他持有这行 S 的核心把自己的副本改成 I
  • A 获得独占写权限后,S -> M

1.3.3.4 · 场景 4:别的核心 B 读取 A 的 M#

  • A 持有的是最新值
  • 协议需要让这份最新数据对 B 可见
  • 常见效果是:A 提供/回写数据,自己的状态降级,B 拿到共享副本
  • 最终常见变为 A: S, B: S

1.3.3.5 · 场景 5:别的核心 B 想写 A 的 MS#

  • B 必须先获得独占权
  • A 的那份副本会被置为 I
  • B 成为新的 M

本质上,任意时刻一个 cache line 最多只能有一个 writer

1.3.4 · 4. MESI 依赖什么机制工作?#

常见实现依赖 bus snooping(总线嗅探) 或目录协议。

以 snooping 为例:

  • 一个核心发出读/写意图
  • 其他核心监听这些一致性事件
  • 如果发现自己缓存了同一行,就按协议做状态迁移

也就是说,cache 不只是存数据,还会对外部事件作出反应。

1.3.5 · 5. 为什么 false sharing 会很贵?#

MESI 的一致性粒度通常不是变量,而是 cache line

这意味着:

  • 线程 A 改变量 x
  • 线程 B 改变量 y
  • 即使 xy 是两个完全独立的变量
  • 只要它们落在同一个 cache line 上

两个核心还是会反复触发:

  • 失效对方副本
  • 重新获取独占权
  • cache line 在不同核心之间来回“乒乓”

这就是 false sharing。逻辑上没共享,硬件上却共享了同一行缓存。

1.4 · A 回答问题#

一句话总结:

MESI 通过给每个 cache line 维护 Modified / Exclusive / Shared / Invalid 四种状态,保证多核缓存下“同一时刻最多一个写者,读者不会长期持有旧副本”。

最关键的理解点:

  1. MESI 管的是 cache line 一致性,不是语言级并发语义
  2. E 状态的价值是减少“先读后写”的额外广播成本
  3. S 变成可写,必须先让其他副本失效
  4. M 表示当前 cache 持有唯一且最新的数据副本
  5. false sharing 本质上是 cache line 粒度一致性带来的副作用

如果是从工程角度理解它,最有用的结论通常只有两个:

  • 多线程性能问题,很多时候不是锁太慢,而是 cache line 在打架
  • 做并发数据结构或性能优化时,要同时考虑“共享变量”与“共享 cache line”