缓存一致性
复习
- 第三十一章:知道了每一层存储都是下层的缓存
- 第三十二章:了解了缓存如何命中、缺失、替换
TL;DR
- 现代 CPU 有多个核心,每个核心都有自己的缓存
- 同一个数据可能同时存在于多个核心的缓存里
- 一个核心改了数据,其他核心的副本就过期了,这就是缓存不一致
- 解决机制叫缓存一致性协议,常见的有 MESI
正文
多核带来的新麻烦
为了更快,现代 CPU 往往把好几个**核心(Core)**做在一颗芯片里,每个核心都能独立干活。为了各自都快,每个核心都自带一份缓存。
这在单核时代完全没问题,但一到多核,麻烦就来了。
一个例子
假设变量 X 现在的值是 1,同时被两个核心用着:
核心 A 的缓存:X = 1
核心 B 的缓存:X = 1
内存: X = 1
这时核心 A 把 X 改成 2:
核心 A 的缓存:X = 2 ← 改了
核心 B 的缓存:X = 1 ← 还是旧值!
内存: X = 1 ← 也还是旧值
现在核心 B 去读 X,它从自己的缓存里拿到 1,以为一切正常——可实际上 X 早就是 2 了。这就是缓存不一致(Cache Incoherence)。
问题的根源是:同一份数据,被抄了好几份。
怎么解决
最直观的思路:谁要改数据,就先把别人的副本作废。
流程大概是:
- 核心 A 要写
X,先通过总线广播一声“我要改 X 了“ - 其他核心听到后,把自己缓存里的
X标成“作废“ - 核心 A 放心地改
- 之后核心 B 再读
X,发现自己的副本作废了,只好重新去取最新值
这种“靠总线广播、大家互相监听“的机制,叫总线监听(Bus Snooping)。有了它,虽然每个核心还是有各自的缓存,但大家看到的数据始终是同一份最新的。
MESI 协议
现实中,一致性协议比上面的描述精细得多,常见的一种叫 MESI。它给每个缓存块记一个状态:
- M(Modified,已修改):我改过,和内存不一样,只有我有最新值
- E(Exclusive,独占):只有我有,而且和内存一致
- S(Shared,共享):好几个核心都有,都一致
- I(Invalid,无效):我的副本作废了,不能用
核心在读写时,根据自己和他人的状态,在 M、E、S、I 之间切换,从而在“性能“和“一致“之间取得平衡。具体状态图可以很复杂,但核心思想就是我们上面说的那一条:改之前先让别人作废。
为什么它很重要
缓存一致性是多核并行的地基。如果两个核心各算各的、看到的还是不同的数据,那并行程序根本没法保证正确。
但也别高兴太早:一致性协议只能保证“大家看到同一份数据“,并不能保证“读写顺序符合你的预期“。所以并行编程还需要同步机制(锁、信号量等)来配合。这些内容,会在讲并行计算和操作系统时再展开。
思考题
缓存一致性靠“广播 + 互相监听“实现。如果 CPU 的核心数越来越多(比如几十上百个),这种广播方式会遇到什么麻烦?
小结
知识点
- 多核与各自的缓存
- 缓存不一致问题的由来
- 总线监听
- MESI 协议的四种状态
- 一致性与并行编程的关系
参考资料
- Wikipedia(zh):缓存一致性:缓存一致性问题
- Wikipedia(zh):MESI协议:MESI 一致性协议
思考题答案(仅供参考)
广播的开销会随核心数迅速膨胀:核心越多,一次写就要通知越多的人,总线上消息拥挤,延迟也变大。所以大规模多核系统往往不再用简单的总线监听,而改用**基于目录(Directory)**的协议——只通知那些真的存了这份数据的核心,避免全广播。这又是“加一层、做索引“的老思路。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪