Java多线程系列(2)
多线程下存在的问题
在多处理器系统中,每个处理器都有自己的高速缓存,而它们又共享同一主内(MainMemory)。基于高速缓存的存储交互很好地解决了处理器与内存的速度矛盾,但是也引入了新的问题:缓存一致性(CacheCoherence)。当多个处理器的运算任务都涉及同一块主内存区域时,将可能导致各自的缓存数据不一致的情况,如果真的发生这种情况,那同步回到主内存时以谁的缓存数据为准呢?为了解决一致性的问题,需要各个处理器访问缓存时都遵循一些协议,在读写时要根据协议来进行操作,这类协议有MSI、MESI(IllinoisProtocol)、MOSI、Synapse、Firefly DragonProtocol,等等。
缓存一致性协议(MESI)
官方定义:MESI协议是基于Invalidate的高速缓存一致性协议,并且是支持回写高速缓存的最常用协议之一。
个人理解:说白了就是为了保证主内存和多核CPU或者多CPU里面的缓存的数据在同一时刻都保持一致。如果是单CPU的单核的的就不需要使用缓存一致性协议,因为始终就一个CPU去访问数据不存在不一致的问题。
为什么要使用MESI协议?
首先我们来看一个场景:如下图所示
如上图所示,当两个线程T1属于CPU1,T2属于CPU2同时访问主内存中的共享变量X=5时,首先回去各自的缓存中去找,如果缓存中不存在就去主内存中去找。
然后T1执行了一次加法运算,此时X的值变为6,然后此时T2也执行了一次加法,按道理来说此时X的值应该为7,但是实际情况下在CPU2的缓存中的值依然是X等于6.也就是说CPU2并没有嗅探到CPU1对变量X=5进行了一次加法运算,而是按照自己缓存中存在的数进行了加1运算。假如CPU1立马把值刷回到主内存中,但是对于CPU2来说他依然的结果是6?为啥呢?因为一开始他们两个同时把X=5加载到了自己的缓存中去了,所以首先去缓存中找,如果缓存中存在就直接加载到寄存器里了不在去主内存找了,然后计算结果是6,刷到缓存中。
从上面的问题我们可以看出在多CPU或者多核CPU中存在主内存和各个缓存的数据不一致的问题?为了解决这个问题,提出了两种办法一是:总线加锁,二是:使用MESI协议(在这里我们之论述MESI)。如下图所示:
其中缓存行是缓存中的最小存储单元,里面存储的是从主内存中加载的共享变量。
缓存一致性协议主要包括缓存行的的四种状态:
M: 被修改(Modified)
该缓存行只缓存在该CPU的缓存中,并且是被修改过的(dirty),即与主存中的数据不一致,该缓存行中的共享变量需要在未来的某个时间点写回(write back)主存。当被写回主存之后,该缓存行的状态会变成独享(exclusive)状态。
E: 独享的(Exclusive)
该缓存行只被缓存在该CPU的缓存中,它是未被修改过的(clean),与主存中数据一致。该状态可以在任何时刻变成共享状态(shared),比如当其它CPU读取内存中的值时。同样地,当该CPU修改该缓存行中内容时,该状态可以变成Modified状态。
S: 共享的(Shared)
该状态意味着该缓存行中的共享变量可能被多个CPU缓存,并且各个缓存中的数据与主存数据一致(clean),当有一个CPU修改自己缓存行中的内容时中,其它CPU中的缓存行中的共享变量可以被作废(变成无效状态(Invalid))。
I: 无效的(Invalid)
该缓存是无效的(可能有其它CPU修改了该缓存行)。
每个CPU都会时刻监听总线,都会时刻监听到这个共享变量在某一时刻处于哪个状态,以便对自己缓存中的共享变量进行更新或者其它操作。
操作:
一个写请求只有在该CPU的缓存行是M或者E状态时才能被执行,如果缓存行处于S状态,必须先将其它缓存中该缓存行变成Invalid状态(也既是不允许不同CPU同时修改同一缓存行,即使修改该缓存行中不同位置的数据也不允许)。该操作经常作用广播的方式来完成,例如:Request For Ownership (RFO)
所有的状态转换如下所示:
一个处于S状态的缓存行也必须监听其它缓存,当其它缓存行的内容被修改时,立刻把自己的缓存行变成无效(Invalid)。
一个处于E(就说明他修改了,然后把值刷回到了主内存中,此时它和主内存中的数据是一致的)状态的缓存行也必须监听其它缓存,所以当其它缓存读主存中的值时,一旦有这种操作,该缓存行需要变成S状态。
一个处于M状态的缓存行也必须时刻监听总线,M(Modified)和E(Exclusive)状态的缓存行,数据是独有的,不同点在于M状态的数据是dirty的(和内存的不一致),E状态的数据是clean的(和内存的一致)。
M状态和I状态示例如下:
Core 0修改了x的值之后,这个Cache line变成了M(Modified)状态,其他Core对应的Cache line变成了I(Invalid)状态。
在MESI协议中,每个Cache的Cache控制器不仅知道自己的读写操作,而且也监听(snoop)其它Cache的读写操作。每个Cache line所处的状态根据本核和其它核的读写操作在4个状态间进行迁移。