CPU多核缓存架构java
一、多线程环境下存在的问题
缓存
在多处理器系统中,每一个处理器都有本身的高速缓存,而它们又共享同一主内存(RAM)。基于高速缓存的存储交互很好地解决了处理器与内存的速度矛盾,可是也引入了新的问题:缓存一致性(CacheCoherence)。多线程
如有两个线程 T1 和 T2 都去计算 x + 1的值(x初始值为1),T1线程的由 CPU1去处理, T2 线程由CPU2去处理;CPU1和CPU2的高速缓存中的副本都是 x = 1,通过CPU加1操做后,再次放入的CPU一、CPU2的高速缓存中的数都为 x = 2 ,当数据放入到主内存中的时候,就引起了缓存不一致性的问题;架构
为了解决一致性的问题,须要各个处理器访问缓存时都遵循一些协议(缓存一致性协议),在读写时要根据协议来进行操做,这类协议有MSI、MESI(IllinoisProtocol)、MOSI、Synapse、Firefly及DragonProtocol,等等。解决一致性的问题也可使用总线加锁的方式,当CPU1读主内存同一段逻辑空间时,给总线加一把锁,CPU2读写的时候都没有办法进行,这样也能够解决该问题,可是这样的效率过低,通常不使用这种方式优化
二、MESI缓存一致性协议 spa
CPU缓存的最小单位:缓存行(Cache line),有可能32字节、64字节、128字节,根据CPU来定;线程
(1)MESI的介绍blog
(2) MESI缓存一致性协议的工做原理排序
以上面多线程中计算 x 加1的操做为例;内存
(1)T1 从主存中读取 x = 1,放到 CPU1 的缓存,状态标记为独占(E),而且 CPU1 时刻监听总线当中其余CPU对这块内存的操做(总线嗅探机制);
(2)若 T1 计算以后尚未会写到主存中,此时 T2 由CPU2计算x加1的操做,从主存中读取x = 1,放入CPU2的缓存中;
(3)当 T2 经过总线从 主存中读取了 x = 1,T1的CPU1经过总线嗅探机制知道了T2从主存中读取了同块内存,则将 CPU1 中的状态修改成共享(S),
CPU2 中的状态标记为共享(S),同时 CPU2 时刻监听总线当中其余CPU对这块内存的操做;
(4)此时,T1经过 CPU1 计算完成了的结果:x = 2,须要将结果回写到主存中;首先锁住CPU1的缓存行,将CPU1 的缓存的状态标记为修改(M),接着发送消息给总线,其余的CPU一直在嗅探,嗅探到消息以后将其余的CPU的缓存状态标记为无效(I),此时CPU2的缓存状态被修改的无效;
(5)接着,CPU1 将计算结果 x = 2 回写到主存中(主存中的数据此时变为 x = 2),接着CPU1 将缓存的状态标记为独占(E), 丢弃掉其余CPU缓存状态为无效的数据;
(6)接着CPU2 从新从主存中读取数据(x = 2),经过总线嗅探机制CPU1又嗅探到CPU2读取了主存中的这块内存区域,则将 CPU1的缓存状态和CPU2的缓存状态都设置为共享(S)....
失效指的是缓存行的失效,
若两个CUP同一个时间都去修改主存中 x 的值,则在一个指令周期内会进行裁决,一个认为有效,另外一个认为无效;有一个裁决为无效了,不必定再次去主存中读取了,取决于程序的写法;
好比在java中,变量前面增长 volatile 关键字,则在底层就去要遵照缓存一致性协议;
(3)什么状况下缓存一致性协议会失效?
状况一:
若是x 的存储长度大于缓存行的存储单元;数据存储横跨两个以上的缓存行,没有办法去给缓存行去加锁了,只能经过总线加锁的方式;
状况二:
CPU自己不支持缓存一致性协议,好比早期的奔腾系列CPU;
三、指令重排序问题
为了使得处理器内部的运算单元能尽可能被充分利用,处理器可能会对输入代码进行乱序执行(Out-Of-Order Execution)优化,处理器会在计算以后将乱序执行的结果重组,保证该结果与顺序执行的结果是一致的,但并不保证程序中各个语句计算的前后顺序与输入代码中的顺序一致。所以,若是存在一个计算任务依赖另外一个计算任务的中间结果,那么其顺序性并不能靠代码的前后顺序来保证。与处理器的乱序执行优化相似,Java虚拟机的即时编译器中也有相似的指令重排序(Instruction Reorder)优化。