【Java并发编程与高并发解决方案】CPU多级缓存与缓存一致性(MESI协议)

最近用到了很多多线程的问题,发现对于并发的理论只是还有些欠缺,在学习的时候也会慢慢积累一下文章~希望能跟更多的小伙伴更好的交流~

目录

 

概述以及抛出几个问题

为什么需要CPU多级缓存

为啥要需要缓存Cache

cache容量有限,命中率低,为啥还要他。

缓存一致性 MESI

开始详细说一下cpu、内存、缓存之间的关系

如果只是读缓存的情况

如果考虑写缓存的情况

一致性协议

MESI缓存一致性协议


概述以及抛出几个问题

  • 为什么需要CPU多级缓存

  • 一级缓存太贵而且容量小,所以有有了二级、三级缓存

    【Java并发编程与高并发解决方案】CPU多级缓存与缓存一致性(MESI协议)_第1张图片

     

     

  • 为啥要需要缓存Cache

    【Java并发编程与高并发解决方案】CPU多级缓存与缓存一致性(MESI协议)_第2张图片

  • cache容量有限,命中率低,为啥还要他。

     

    【Java并发编程与高并发解决方案】CPU多级缓存与缓存一致性(MESI协议)_第3张图片

  • 缓存一致性 MESI

     

    【Java并发编程与高并发解决方案】CPU多级缓存与缓存一致性(MESI协议)_第4张图片

开始详细说一下cpu、内存、缓存之间的关系

  • 绝大多数 的内存访问都需要通过层层的缓存来进行

  • CPU正常情况下不能直接访问内存,物理决定。只能通过多级缓存来访问内存

  • 先来个笼统一点的概念
    • 缓存段,一段和缓存大小对其的内存。

如果只是读缓存的情况

这种情况下,任意时刻,缓存中缓存段的内容和内存中对应的内容是一致的。

  • cpu首先发出读指令,把内存地址给到cache

  • cache会检查是否有这个地址对应的缓存段,如果没有,会从内存(或者更下一级,二级之类的缓存)中把这一个缓存段全部存到一级缓存中。
  • 数据被加在到一级缓存后,cpu就可以正常读取了

如果考虑写缓存的情况

记住一条:当所有脏数据都被回写后,任一级别缓存中所有缓存段的数据和内存中对应的数据是一致的。

  • 写的情况,分为直写(write-through)和回写(write-back)
    • 直写:如果对应缓存中的段被更新了,那他的下一级缓存或者内存也应该同步被更新。
    • 回写:如果对应缓存中的段被更新了,不会立即被写到下一级缓存或者内存,而是被标记为了脏段。脏段会在特定的时刻(具体啥时候下面会详细说)触发回写。也就是说,脏段在被丢弃的时候,总是要先进行一次回写。
  • 这个地方就没有任何时刻了,因为回写模式有脏数据的存在。
  • 虽然直写看上去更简单,但是回写模式可以避免对同一片地址的反复操作,可以一次性写一大片内存。

一致性协议

上面扯的那些都是在只有一组缓存的情况下,如果有多个cpu对应多组缓存,如果某一组缓存段对应的内存地址中的数据被另一个cpu给修改了,会发生什么。

  • 那啥,很遗憾的是,如果被其他的cpu给修改了,另外的缓存是无法得知这一更改的,这就破坏了一致性。
  • 为啥不是多核cpu只对应一组缓存呢。
    • 当然是效率问题啦,并发情况下出现问题不都是因为效率问题导致的吗。
    • 因为在每一个时钟或者指令周期,都只能有一个cpu通过缓存对内存进行操作,其他的cpu就进入了等待。流水线就产生了冒泡。会极大浪费硬件资源~
  • 所以引入多组缓存,使他们用起来的效果像只有一组缓存一样,这个协议就是保持了多组缓存之间的一致性。
  • 这个缓存一致性协议有很多种,最常用的是窥探snooping协议。
    • 内存在这里是共享资源,所有cpu对于内存的操作都要经过仲裁:
      • 所有对于内存的操作都经过同一条总线,所有的缓存组都挂在这个总线上。
      • 缓存不只有在与内存发生数据交换的时候才和总线打交道,他会无视无可关注其他处理器对于内存的操作。
      • 当一个处理器通过缓存执行了对于内存的修改,其他的缓存组就可以窥探到这一个操作,使自己的缓存中对应的缓存段失效或者更新。

MESI缓存一致性协议

  • 上面说的那种方式,在直写模型中没有任何问题,但如果到了回写模型,就会有问题了。
    • 有可能在某一组缓存中已经产生了脏数据,但是此刻并没有发生回写,所以其他内存组没办法知道这一变化,还是拿了内存中的数据。
    • 这时候只需要做的是,在产生脏数据之前,要把这一变化sync到其他的缓存组。这就是MESI协议啦。
  • MESI是Modified、Exclusive、Shared、Invalid的首字母缩写,代表四种缓存状态。下面就详细说一下这四种状态。

    • Invalid 失效缓存段,这种状态下可以认为是缓存中没有这个内存地址对应的缓存段。
    • Shared 共享缓存段。是一份和主存中一摸一样的拷贝。这时候只能读不能写。多组缓存段都拥有对于这个数据的拷贝。
    • Exclude 独占缓存段。有一份与主存一摸一样的拷贝。区别在于其他缓存段不能同时持有他,当某个缓存段处于这一状态,其他缓存段中,对应统一内存地址的缓存段就变为了Invalid状态。
    • Modified 修改缓存段。此时缓存段的状态是脏段,其他处理器缓存中的拷贝都变为Invalid。如果此段缓存要失效或被丢弃,需要把数据回写到内存。
  • 好啦,上面比比了那么多,一开始所说的“一组缓存在修改前,如何通知到其他缓存的问题”已经得到了解决。

    • 只有在M或者E状态下(获取了独占权)的缓存才能被进行写操作,如果没有获取到独占权,就要通知总线上的其他缓存,此时其他处理器缓存中对应的数据就变为了Invalid状态。
    • 如果有其他处理器想读这块内存,那么M状态下写的数据就会被写回到内存,这时候就变成了Shared状态。

1.在多核系统中,读取某个缓存段,实际上会牵涉到和其他处理器的通讯,并且可能导致它们发生内存传输。写某个缓存段需要多个步骤:在你写任何东西之前,你首先要获得独占权,以及所请求的缓存段的当前内容的拷贝(所谓的“带权限获取的读(Read For Ownership)”请求)。

2.尽管我们为了一致性问题做了额外的工作,但是最终结果还是非常有保证的。即它遵守以下定理,我称之为:MESI定律

  • 在所有的脏缓存段(M状态)被回写后,任意缓存级别的所有缓存段中的内容,和它们对应的内存中的内容一致。此外,在任意时刻,当某个位置的内存被一个处理器加载入独占缓存段时(E状态),那它就不会再出现在其他任何处理器的缓存中。

参考文章:

        Cache coherency primer:https://fgiesen.wordpress.com/2014/07/07/cache-coherency/

你可能感兴趣的:(并发编程)