缓存一致性(Cache Coherency)问题
不知道小伙伴们有没有想过这样的问题:内存模型到底是怎么保证缓存一致性的呢?
接下来我们试着回答这个问题。首先,缓存一致性是由于引入缓存而导致的问题,所以,这是很多 CPU 厂商必须解决的问题。为了解决前面提到的缓存数据不一致的问题,人们提出过很多方案,通常来说有以下 2 种方案:
- 通过在总线加
LOCK#
锁的方式。 - 通过缓存一致性协议(Cache Coherence Protocol)。
总线加锁
在早期的 CPU 中,是通过在总线上加 LOCK#
锁的形式来解决缓存不一致的问题。因为 CPU 和其他部件进行通信都是通过总线来进行的,如果对总线加 LOCK#
锁的话,也就是说阻塞了其他 CPU 对其他部件访问(如内存),从而使得只能有一个 CPU 能使用这个变量的内存。在总线上发出了 LCOK#
锁的信号,那么只有等待这段代码完全执行完毕之后,其他 CPU 才能从其内存读取变量,然后进行相应的操作。这样就解决了缓存不一致的问题。
但是由于在锁住总线期间,其他 CPU 无法访问内存,会导致效率低下。因此出现了第二种解决方案,通过缓存一致性协议来解决缓存一致性问题。
缓存一致性协议(Cache Coherence Protocol)
缓存一致性协议(Cache Coherence Protocol),最出名的就是 Intel 的 MESI 协议,MESI 协议保证了每个缓存中使用的共享变量的副本是一致的。
MESI 的核心的思想是:当 CPU 写数据时,如果发现操作的变量是共享变量(即在其他 CPU 中也存在该变量的副本),则会发出信号通知其他 CPU,以让它们将该变量的缓存行置为无效状态,因此当其他 CPU 需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
在 MESI 协议中,每个缓存可能有有 4 个状态,它们分别是:
- M(Modified):这行数据有效,数据被修改了,和内存中的数据不一致,数据只存在于本 Cache 中。
- E(Exclusive):这行数据有效,数据和内存中的数据一致,数据只存在于本 Cache 中。
- S(Shared):这行数据有效,数据和内存中的数据一致,数据存在于很多 Cache 中。
- I(Invalid):这行数据无效。
关于 MESI 的更多细节这里就不详细介绍了,读者只要知道,MESI 是一种比较常用的缓存一致性协议,他可以用来解决缓存之间的数据一致性问题就可以了。
但是,值得注意的是,传统的 MESI 协议中有两个行为的执行成本比较大。
- 一个是将某个 Cache Line 标记为 Invalid 状态;
- 另一个是当某 Cache Line 当前状态为 Invalid 时,写入新的数据。
所以 CPU 通过 Store Buffer 和 Invalidate Queue 组件来降低这类操作的延时,如图:
当一个 CPU 进行写入时,首先会给其它 CPU 发送 Invalid 消息,然后把当前写入的数据写入到 Store Buffer 中。然后异步在某个时刻真正的写入到 Cache 中。
当前 CPU 核如果要读 Cache 中的数据,需要先扫描 Store Buffer 之后再读取 Cache。
但是此时其它 CPU 核是看不到当前核的 Store Buffer 中的数据的,要等到 Store Buffer 中的数据被刷到了 Cache 之后才会触发失效操作。
而当一个 CPU 核收到 Invalid 消息时,会把消息写入自身的 Invalidate Queue 中,随后异步将其设为 Invalid 状态。
和 Store Buffer 不同的是,当前 CPU 核心使用 Cache 时并不扫描 Invalidate Queue 部分,所以可能会有极短时间的脏读问题。
所以,为了解决缓存的一致性问题,比较典型的方案是 MESI 缓存一致性协议。
MESI 协议,可以保证缓存的一致性,但是无法保证实时性。
Reference
- 内存模型是怎么解决缓存一致性问题的? - https://www.hollischuang.com/archives/2662
- 缓存一致性(Cache Coherency)入门 - https://www.infoq.cn/article/cache-coherency-primer