介绍下cpu缓存一致性(MESI协议)

电子说

1.2w人已加入

描述

介绍

之前介绍了java并发包的cas原理和java内存模型,这篇我们介绍下cpu缓存一致性原理,可以帮助我们更好的理解cas的底层原理。

一、cpu多级缓存结构

JAVA语言

  • 计算机在寄存器上执行的速度是远大于在主内存上执行的速度。

  • 由于计算机的存储设备与处理器的运算速度之间存在几个数量级的差距,所以新的计算机系统都不得不加入一层读写速度都尽可能接近处理器运算速度的高级缓存来作为内存与处理器之间的缓冲,将运算使用到的数据复制到缓存中,让运算快速执行,当运算结束后,再将数据从缓存同步到内存中,这样处理器就无需等待缓慢的内存读写了。

  • 一个计算机还包含一个主存,所有的cpu都可以访问这个主存,主存通常比CPU中的缓存大得多。

  • 多核cpu运作原理:

    通常情况下,当一个CPU需要读取主存的时候,它会将主存的数据读取到CPU缓存中,甚至会将缓存中的部分内容读到它内部的寄存器里面,然后在寄存器中执行操作;当CPU需要将结果回写到主存的时候,它会将内部寄存器的值刷新到缓存中,然后在某个时间点将值刷新回主存。

二、MESI协议

JAVA语言

四种状态:

  • M:Modified(被修改)

    指的是该缓存行只被缓存在该CPU的缓存中,并且是被修改过的,因此它与主存中的数据是不一致的,该缓存行的内存需要在未来的某个时间点写回主存,这个时间点我们是允许其他CPU读取主存中相应的内存之前,当这里的值被写回主存之后,该缓存行的状态变成E。

  • E:Exclusive(独享)

    独享状态的缓存行只被缓存在该CPU的缓存中,它是未被修改过的,是与主存中的数据一致的,这个状态可以在任何时刻,当有其他CPU读取该内存时变成S。同样的,当COU修改该缓存行的数据时,该状态可以变成M。

  • S:Shared(共享)

    共享状态意味着,该缓存行可能被多个CPU进行缓存,并且各个缓存中的数据与主存中的数据是一致的,当有一个CPU修改该缓存行的时候,其他CPU中该缓存行是可以被作废的,变成I。

  • I:Invalid(无效的)

    无效状态代表这个缓存是无效的,可能是有其他CPU修改了该缓存行。

CPU的cache的四种操作可能产生不一致的状态,因此缓存控制器监听到本地操作和远程操作的时候,需要对地址一定的cache line做出一定的修改,从而保证数据在多个缓存之间的一致性。

四种操作:

  • local read

    代表的是读本地缓存行中的数据。

  • local write

    代表的是将数据写入到本地的缓存里面。

  • remote read

    代表的是将内存中的数据读取到本地。

  • remote write

    代表的是将数据写回到主存中。

三、MESI工作原理

在一个典型的多核系统中,每一个核都会有自己的缓存来共享主存总线,每一个相应的CPU都会发出读写请求,而缓存的目的是为了减少CPU读写共享主存的次数。

  • 一个缓存除了在Invalid状态之外,都可以满足CPU的读请求;
  • 一个写请求,只有在该缓存行是M状态或者E状态,才能被执行,如果当前状态是处于S状态,它必须先将这个缓存中的该缓存行变成无效的状态,这个操作一般采取广播的方式来完成,这个时候,它不允许不同的CPU同时修改同一个缓存行,主要是为了解决缓存一致性的问题;
  • 一个处于M状态的缓存行,它必须时刻监听所有试图读该缓存行的操作,这种操作必须将缓存写回到主内存,并将状态修改为S之前被延迟执行;
  • 一个处于S状态的缓存行,也必须监听其他缓存只被缓存行无效,或者独享该缓存行的请求,并将缓存行变成无效;
  • 而一个处于E状态的缓存行,它要监听其他缓存读缓存中该缓存行的操作,一旦有该缓存行的操作,它需要将E状态修改为S状态;

因此对于M和E,它的数据总是精确的,而S状态可能是非一致的,如果一个缓存将处于S状态的缓存行作废了,另一个缓存实际上可能已经独享了该缓存行,但是该缓存却不会将缓存行变更为E状态,这是因为其他缓存不会广播他们作废掉该缓存行的通知,同样,由于缓存并没有保存该缓存行的topic数量,因此也没有办法确认自己是否已经独享了该缓存行。

如果一个CPU想修改处于S状态的缓存行,总线事务需要将所有该缓存行topic的值变成Invalid状态才可以,而修改E状态的缓存不需要使用总线事务。

四、MESI在并发包中的应用

在cas原理分析中,我们讲到cas底层原理使用到了总线锁和缓存锁,其中缓存锁涉及到的就是MESI协议,缓存锁加锁条件就是缓存行处于M和E状态下。

下面结合volatile保证内存可见性为例,阐释下MESI的工作原理。代码示例:

public class VolatileCanSeeTest {


    private static boolean initFlag = false;


    public static void main(String[] args) throws InterruptedException {
        new Thread(() - > {
            log.info("init begin");
            while(!initFlag) {
            }


            // if(!initFlag) {while(true){}} // JIT
            log.info("===success===");
        }).start();


        Thread.sleep(1000);


        new Thread(() - > doSomething()).start();
    }


    public static void doSomething() {
        log.info("doSomething begin");
        initFlag = true;
        log.info("doSomething end");
    }
}

JAVA语言

  • 初始化共享变量initFlag = false。
  • 线程1通过read指令从主内存中读取出共享变量initFlag = false,通过load指令加载到线程1的工作内存中。
  • 在线程1的工作内存中,通过use指令将共享变量initFlag = false加载到cpu执行引擎进行!initFlag运算。
  • 在线程1的工作内存中,通过assign指令将cpu执行引擎计算后的共享变量initFlag = true赋值到线程1的工作内存中。
  • 线程1通过store指令将线程1中工作内存的共享变量同步到主内存中。
  • 在线程1通过store指令将线程1中工作内存的共享变量同步到主内存中的过程中,会经过总线,触发cpu缓存一致性协议。
  • 该协议会监听回写主内存的变量,然后将其他工作内存中含有该共享变量的缓存行状态置为失效状态,所以其他线程需要重新从主内存读取该共享变量的最新值。
  • 以上我们分析了MESI协议中关于S共享状态转为I失效状态的工作原理。

结语

上一篇我们分析了java内存模型的原理,这篇总结下cas涉及到的cpu缓存一致性协议,通过这两篇的介绍,我们就可以更深刻的理解cas是怎么保证原子性的。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分