缓存一致性(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