什么是 MESI 缓存一致性协议?

#计算机网络#操作系统#面试八股

什么是 MESI 缓存一致性协议?

MESI 是多核 CPU 保证缓存一致性的状态机协议,用四个状态描述每条缓存行(cache line,通常 64 字节): Modified(已修改)、Exclusive(独占)、Shared(共享)、Invalid(无效)。核心规则只有一条: 写之前必须先拿到独占权,其他核的副本会被总线消息置为 Invalid,从而避免两个核同时写同一地址导致数据不一致。

1. 四个状态

状态本核有副本与其他核一致内存中是最新值含义
M是否(只有我有)否(脏)我改过,别人都没有
E是是(只有我有)是独占且干净,可直接改
S是是(可能多核共享)是只读共享
I无效——副本作废,用之前必须重新取

2. 状态转移

            本地读                  本地写
   I ───────────────► S/E    I ───────────────► M(发 BusRdX,让别人失效)
   S ───────────────► S      S ───────────────► M(发 BusUpgr,只失效不取数)
   E ───────────────► E      E ───────────────► M(无总线消息,直接改)
   M ───────────────► M      M ───────────────► M

            其他核读(BusRd)        其他核写(BusRdX)
   M ───────────────► S(先回写内存) M ───────────────► I(先回写)
   E ───────────────► S              E ───────────────► I
   S ───────────────► S              S ───────────────► I
   I ───────────────► I              I ───────────────► I

关键的三个总线事务:BusRd(我要读,谁能给我)、BusRdX(我要写,请所有人失效)、 BusUpgr(我要写,但我已有副本,只需你们失效)。E 状态的价值就在这:独占干净时写不需要任何总线通信。

3. 伪共享(false sharing)

缓存以行为单位,两个变量落在同一行(64 字节内),即使逻辑上无关,也会互相把对方的缓存行打无效:

struct { a int64; b int64 }   // a、b 在同一 cache line
 核0 写 a → 该行变 M
 核1 写 b → BusRdX → 核0 的行变 I
 核0 再写 a → 又要 BusRdX … 来回 ping-pong,性能暴跌
// 反例:两个 goroutine 各加一个字段,却共享同一缓存行
type Bad struct{ a, b atomic.Int64 }

// 正例:用 padding 把两个字段隔到不同缓存行(64B)
type Padded struct {
    a atomic.Int64
    _ [56]byte      // 8 + 56 = 64,b 落到下一行
    b atomic.Int64
}

func BenchmarkFalseSharing(b *testing.B) {
    var c Bad
    var wg sync.WaitGroup
    wg.Add(2)
    go func() { for i := 0; i < b.N; i++ { c.a.Add(1) }; wg.Done() }()
    go func() { for i := 0; i < b.N; i++ { c.b.Add(1) }; wg.Done() }()
    wg.Wait()
}
# 定位伪共享:看 cache line 的 HITM(命中他人已修改行)次数
perf c2c record -a -- sleep 5
perf c2c report --stdio | head -40

真实案例:Java 的 @Contended、Go 的 sync.Pool 里的 pad、Linux 内核的 ____cacheline_aligned, 都是为了把热点变量隔离到独立缓存行。

4. 常见追问

Q:有了 MESI 为什么还需要内存屏障? MESI 保证的是"缓存行最终一致",但每个核都有 store buffer 和无效队列,写操作可能延迟可见。 volatile/atomic 背后的 mfence、lock 前缀负责在必要时清空 store buffer、等待无效队列处理完, 从而提供顺序保证。一致性(coherence)≠ 内存序(consistency)。

Q:MESI 和 MOESI 差在哪? MOESI 多了 O(Owned)状态:多个核共享脏数据时,由 O 状态的核负责回写内存,避免"一写就要写回内存", AMD 与部分 ARM 实现采用。Intel 主流是 MESI 的变体 MESIF(F 表示 Forward,指定一个核负责响应读)。

Q:为什么缓存行是 64 字节? 空间局部性与总线效率的折中:行太小则标记开销大、总线事务多;行太大则伪共享更严重、传输粒度粗。 64 字节也是主流 DRAM 的突发长度。

Q:MESI 能解决 ABA 或原子性问题吗? 不能。它只保证单地址的读写一致性;复合操作(读-改-写)要靠 lock cmpxchg/CAS 这类原子指令, MESI 只是让这些指令的失效广播更高效。

小结

MESI 用 M/E/S/I 四态和 BusRd/BusRdX/BusUpgr 三类总线事务,保证"写前独占、写后他核失效"。 它带来一个经典副作用——伪共享:无关变量共享缓存行会引发 ping-pong。 工程对策就是 padding 隔离热点 + perf c2c 定位。答题时把状态表、转移图、伪共享示例讲全即可。

笔记加载中…