CPU缓存---致性协议剖析

发布时间:2024年01月21日
文章目录

前言

首先,我们需要了解什么是缓存一致性。多处理器系统中的每个处理器都有自己的缓存,用于存储最近使用的数据,以减少内存访问的延迟。然而,这引入了一个问题:当多个处理器修改共享数据时,其他处理器的缓存可能会变得过时。
在这里插入图片描述

一.缓存一致性经典问题

并发读写导致的数据不一致问题

假设有两个线程A和B,它们共享同一个数据项D。在初始化时,D的值为0。

  1. 线程A读取D的值为0,然后将其加1,得到1。
  2. 在此过程中,线程B也读取D的值为0,然后将其加1,得到1。
  3. 线程A将计算得到的值1写回D。
  4. 线程B也将计算得到的值1写回D。

在这个过程结束后,我们希望D的值为2,但实际上D的值为1。这是因为线程A和线程B同时读取了D的初始值,然后各自进行了计算和写回,彼此的计算结果并未互相影响。这就是并发读写导致的数据不一致问题。

需要缓存一致性协议的实例

在多处理器系统中,每个处理器都有自己的缓存。假设有两个处理器P1和P2,它们共享同一个数据项D,D的初始值为0。

  1. 处理器P1将D读入其缓存,然后将其加1,得到1,此时D在P1的缓存中的值为1,而在P2的缓存和主存中的值仍为0。
  2. 处理器P2将D读入其缓存,然后将其加1,得到1,此时D在P2的缓存中的值为1,而在P1的缓存和主存中的值仍为0或1。

在没有缓存一致性协议的情况下,如果P1和P2分别将其缓存中的D的值写回主存,那么最终D的值可能为1,而不是我们希望的2。而缓存一致性协议的目标就是解决这个问题,它通过一系列的机制确保在多处理器系统中,任何时刻,对任何数据项的读写都能得到一致的结果。

缓存一致性协议是解决这个问题的一种方法。

二.常见协议

有许多不同的缓存一致性协议,包括MESI,MOESI和MSI。

大家不要搞混Java内存模型和缓存一致性协议是两个不同的层次的概念,Java内存模型是在语言层次上定义的,而缓存一致性协议是在硬件层次上实现的。作为Java开发者,我们应该专注于理解和使用Java内存模型,而不是试图直接操作硬件级别的缓存一致性协议

1. MESI (Modified, Exclusive, Shared, Invalid)

MESI协议是最常见的协议之一,它定义了四种状态:Modified(修改),Exclusive(独占),Shared(共享)和Invalid(无效)。每个缓存行都标记为其中一种状态,根据处理器对该行的操作状态会改变。

  • Modified: 当前缓存行的数据已被本地处理器修改,并且与内存中的数据不同。如果其他处理器请求这个数据,拥有这个数据的处理器需要将数据写回内存,并且将自己的缓存行标记为Shared。
  • Exclusive: 当前缓存行的数据没有被修改,并且只有本地处理器有这个数据的缓存。
  • Shared: 当前缓存行的数据没有被修改,并且可能被其他处理器缓存。
  • Invalid: 当前缓存行的数据是无效的,或者说与内存中的数据不同。
    在这里插入图片描述
    通俗的讲一下。MESI协议是一种常见的缓存一致性协议,用于处理多处理器系统中的数据共享和一致性问题。它定义了四种状态,分别是修改(Modified)、独享(Exclusive)、共享(Shared)和无效(Invalid)。

想象一下,你和你的朋友都有一本书的副本。这本书代表着一个共享的数据块。

  1. 修改(Modified):当你想要修改书中的内容时,你拥有该书的独家访问权,并且你的朋友的副本已过时。因此,你可以自由地修改书中的内容,而不必担心其他人看到旧版。

  2. 独享(Exclusive):当你只是独自拥有这本书的副本,并且它与主要来源(例如图书馆)保持一致。你可以读取书中的内容,但不能修改它。其他人可能也有相同的书,但与你的副本是独立的,没有冲突。

  3. 共享(Shared):当你和你的朋友都有这本书的副本时,它是共享的。这意味着你们都可以读取书中的内容,而且它们应该是一致的。如果你想要修改书中的内容,你需要先将它标记为修改状态,并通知其他人停止使用他们的副本。

  4. 无效(Invalid):当你的朋友告诉你他们有一本书的新副本时,你的副本就变得无效了。这意味着你的副本已过时或不再可用。你需要丢弃你的副本,并从他们那里获取最新的副本。

MESI协议通过这些状态的管理和转换,确保多个处理器之间共享数据的一致性。当一个处理器想要修改共享数据时,它必须将其状态设置为修改(Modified),同时通知其他处理器使其副本无效。其他处理器在访问共享数据时,会检查自己的副本状态,以确定是否需要更新或共享。这样,数据的一致性得以维护,避免了冲突和不一致的情况。

MESI协议是一种用于处理多处理器系统中缓存一致性的协议。通过定义不同的状态和相应的转换规则,它确保共享数据的一致性,使多个处理器能够正确地访问和更新数据。类比于你和朋友共享一本书的副本,你需要协调修改和访问的顺序,以保证数据的正确性和一致性。

2. MOESI (Modified, Owner, Exclusive, Shared, Invalid)

MOESI协议在MESI的基础上增加了一个Owner状态,用于解决在多个处理器都缓存了同一个块的数据时,数据更新的问题。

  • Owner: 当前缓存行的数据与内存中的数据相同,但可能被其他处理器缓存。如果其他处理器修改了数据,拥有这个数据的处理器需要将数据写回内存。
    在这里插入图片描述
    我们通俗的理解一下

我们继续以图书举例,你和你的朋友都有一本书的副本。当你想要修改书中的内容时,你必须先通知你的朋友,让他/她停止使用他/她的副本。你修改完后,你的朋友可以再次使用他/她的副本。

在MOESI协议中,每个处理器有自己的缓存,用于存储数据的副本。当一个处理器想要修改数据时,它必须首先将缓存中的数据状态设置为"修改"(Modified)。这表示该处理器是唯一拥有该数据的副本,并且数据已被修改但尚未写回主内存。其他处理器的缓存中的副本将被标记为"无效"(Invalid),表示它们不再有效。

当其他处理器需要访问同一份数据时,它们必须检查自己的缓存状态。如果数据在某个处理器的缓存中是"修改"(Modified)状态,那么它必须先将该数据写回主内存,然后再获取最新的副本。如果数据在某个处理器的缓存中是"拥有"(Owned)或"独享"(Exclusive)状态,那么其他处理器可以直接获取该副本而不需要写回主内存。

通过这种方式,MOESI协议确保了数据的一致性。处理器之间通过消息传递和状态转换来协调彼此的操作,以保证每个处理器都能获取到最新的数据副本,并避免冲突和不一致的情况发生。

3. MSI (Modified, Shared, Invalid)

MSI协议是最基础的协议,只定义了三种状态:Modified(修改),Shared(共享)和Invalid(无效)。这是最初级的缓存一致性协议,但在现代处理器中很少使用。

这些协议在Java并发编程中是非常重要的,因为Java内部采用了类似的机制来处理并发读写。Java内存模型(JMM)定义了并发程序中哪些行为是合法的,以及什么时候可以看到哪些效果。JMM与CPU缓存一致性协议的关系在于JMM提供了一种在高层次上管理并发读写的方式,而CPU缓存一致性协议在低层次上实现了这些功能。

虽然大多数Java开发者可能不需要直接处理缓存一致性,但对这些基本概念的了解可以帮助我们更好地理解如何编写有效的并发程序,以及为什么某些程序表现出了预期外的行为。

三种协议对比

协议/特性MESIMOESIMSI
状态数453
状态修改,独占,共享,无效修改,所有者,独占,共享,无效修改,共享,无效
提供者IntelAMD较少应用
优点有独占状态,可以减少在总线上的广播有所有者状态,所有者可以直接向请求者提供数据简单,易于理解
缺点不能直接从其他缓存中获取数据较复杂,需管理所有者状态无独占状态,可能增加总线上的广播

四.java中的影响

假设我们有两个线程,线程A和线程B,他们共享一个变量x。初始时,x = 0。

线程A的代码如下:

x = 1;
  • 1

线程B的代码如下:

System.out.println(x);
  • 1

如果线程A和线程B在不同的处理器上运行,他们各自的处理器可能有各自的缓存副本。在我们的例子中,线程A在其处理器的缓存中将x的值修改为1,然后线程B在其处理器上尝试打印x的值。

根据缓存一致性协议,线程A的处理器需要在某个时刻将它的改动写回主内存,以便线程B的处理器可以看到这个改动。然而,这个"某个时刻"是什么时候呢?如果线程B的处理器在线程A的处理器写回主内存之前读取了x的值,那么它打印的就仍然是0,而不是1。

这就是Java内存模型(JMM)的重要性所在。为了解决这个问题,我们需要在x的写操作和读操作之间建立一个“happens-before”关系。在Java中,我们可以使用synchronized关键字或者volatile关键字来实现。

如果我们将x声明为volatile,如下:

volatile int x = 0;
  • 1

那么线程B就总是可以看到线程A对x的最新改动,这是因为volatile关键字在Java内存模型中有特殊的含义:对一个volatile变量的写操作总是happens-before于后续对这个变量的读操作。

虽然Java程序员不需要直接处理缓存一致性问题,但了解这些概念有助于我们理解并发程序可能出现的奇怪行为,以及如何使用Java内存模型中的工具来编写正确和高效的并发程序。

五.关于缓存一致性协议

MESI,MOESI和MSI都是缓存一致性协议,它们的目标是在多处理器系统中保持缓存的一致性。这些协议是在硬件级别实现的,Java开发人员通常无法直接控制或利用它们。然而,我们可以通过理解这些协议的工作原理,以及它们如何影响多线程程序的行为,来编写更有效的并发代码。

  1. MESI协议:MESI是Modified,Exclusive,Shared,Invalid四种状态的首字母缩写。这个协议的目标是在多处理器系统中,当某个处理器修改了在其缓存中的某个数据项时,能够通过一系列的状态转换,让其他处理器的缓存中的这个数据项无效或者更新为最新的值。

  2. MOESI协议:在MESI的基础上增加了一个Owner状态,当某个处理器修改了在其缓存中的某个数据项后,它就成为这个数据项的Owner,这个数据项在其他处理器的缓存中的状态就变为Invalid。当其他处理器需要这个数据项时,它们就会向Owner请求。

  3. MSI协议:MSI是Modified,Shared,Invalid三种状态的首字母缩写,它是一个较简单的缓存一致性协议。

对于Java开发者来说,他们无法直接使用MESI,MOESI或MSI协议。但是,他们可以利用Java内存模型(JMM)中的语法和语义工具来实现类似的效果。比如,他们可以使用volatile关键字或synchronized关键字来保证一个变量的修改对其他线程的可见性,这在某种程度上类似于缓存一致性协议中的数据项状态转换。

Java内存模型和缓存一致性协议是两个不同的层次的概念,Java内存模型是在语言层次上定义的,而缓存一致性协议是在硬件层次上实现的。作为Java开发者,我们应该专注于理解和使用Java内存模型,而不是试图直接操作硬件级别的缓存一致性协议。

总结

CPU缓存一致性问题主要出现在多核处理器系统中。每个核心都有它自己的缓存,当它们并行处理数据时,如果一个核心修改了它的缓存中的数据,那么其他核心的缓存可能就会变得过时或不一致。

解决CPU缓存一致性问题的主要协议有以下几种:

虽然这些都是硬件层面的事情,但是我们作为开发者应该要具备这些知识。

  1. MESI协议(Modified, Exclusive, Shared, Invalid):这是一种基于写入失效策略的协议。每一个缓存行有四种状态:修改(M)、独占(E)、共享(S)和无效(I)。在任何时候,同一个数据块只能在一个核心的缓存中处于M状态,这就保证了缓存一致性。

  2. MOESI协议(Modified, Owner, Exclusive, Shared, Invalid):这是对MESI协议的扩充,增加了拥有(O)状态。同一个数据块只能在一个核心的缓存中处于O或M状态,使得它可以被其他核心以S状态缓存。

  3. MOSI协议(Modified, Owner, Shared, Invalid):这是一种简化的MOESI协议,去掉了E状态。

以上这些协议使得多核处理器能够在高效地并行处理数据的同时,保持缓存的一致性。

文章来源:https://blog.csdn.net/dgw2648633809/article/details/135717551
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。