volatile 靠的是MESI协议解决可见性问题?(上)

volatile 靠的是MESI协议解决可见性问题?(下)

什么是可见性问题?

实例:main线程和thread 线程都持有flag 这个变量,但是mian线程改变flag值之后,为什么thread 线程的 flag 值没有变化。这是为什么?

public class NoVolatile {
    private boolean flag = true;

    public void test() {
        System.out.println("start");
        while (flag) {

        }
        System.out.println("end");
    }

    public static void main(String[] args) {
        NoVolatile noVolatile = new NoVolatile();
        new Thread(noVolatile::test).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {

        }
        noVolatile.flag = false;
    }
}

//执行结果:
start
thread 和main 线程直接对内存操作没有问题,不会有可见性问题,但是CPU 与内存的数据交互需要经过数据总线和地址总线,效率太低了

在这里插入图片描述
为了减少CPU访问内存所需平均时间,引入了CPU高速缓存。CPU缓存是位于CPU与内存之间的临时数据交换器,它的容量比内存小的多但是交换速度却比内存要快得多

在这里插入图片描述

CPU缓存通常分成了三个级别:L1,L2,L3。级别越小越接近CPU,所以速度也更快,同时也代表着容量越小。

  • L1 是最接近CPU的, 它容量最小(例如:32K),速度最快,每个核上都有一个 L1 缓存,L1 缓存每个核上其实有两个 L1 缓存, 一个用于存数据的 L1d Cache(Data Cache),一个用于存指令的 L1i Cache(Instruction Cache)
  • L2 缓存 更大一些(例如:256K),速度要慢一些, 一般情况下每个核上都有一个独立的L2 缓存
  • L3 缓存是三级缓存中最大的一级(例如3MB),同时也是最慢的一级, 在同一个CPU插槽之间的核共享一个 L3 缓存

高速缓存读取方式:

当处理器发出内存访问请求时,会先查看缓存内是否有请求数据。如果存在(命中),则不经访问内存直接返回该数据;如果不存在(失效),则要先把内存中的相应数据载入缓存,再将其返回处理器。

高速缓存修改方式:

比如现在cpu 对x 进行赋值。根据cache 存不存在x这个缓存块,分为两种做法:
在这里插入图片描述

  • write hit(写命中): 当处理器将操作数写回到一个内存缓存的区域时,首先会检查这个缓存的内存地址是否在缓存行中国,如果存在,就将这个缓存行写入到缓存,而不是写回到内存。
  • Write-misses(写未命中): 此时写入数据不在缓存行的时候:
  1. Write allocate: 方式将写入位置读入缓存,然后采用write-hit(缓存命中写入)操作
  2. No-write allocate: 方式并不将写入位置读入缓存,而是直接将数据写入存储。这种方式下,只有读操作会被缓存。

cache根据写操作后是否要直接同步到内存,可分为write back(稍后同步)和write through(立刻同步)

Write-back(稍后同步):在数据更新时只写入缓存Cache。只在数据被替换出缓存时,被修改的缓存数据才会被写到后端存储。此模式的优点是数据写入速度快,因为不需要写存储;缺点是一旦更新后的数据未被写入存储时出现系统掉电的情况,数据将无法找回。

Write-through(直写模式):在数据更新时,同时写入缓存Cache和后端存储。此模式的优点是操作简单;缺点是因为数据修改需要同时写入存储,数据写入速度较慢

通常Write-back采用Write allocate方式,而Write-through采用No-write allocate方式;因为多次写入同一缓存时,Write allocate配合Write-back可以提升性能;而对于Write-through则没有帮助。(一般采用的是write back 方式)

缓存和内存共存会产生的问题:如何保证修改数据之后,缓存和内存数据一致性问题?

多核系统的“cache一致性”既包括cache和内存之间的一致性,还包括各个CPU的cache之间的一致性。
在这里插入图片描述

Cache 和内存直接的一致性,可以通过write back 去稍后同步,写回到内存去。但是内存直接被改了,缓存如何保持一致还有不同cpu的cache 是如何保持一致的?

cache为维护一致性的操作可分为 flush 和 invalidate

单核cpu cache 和 memory 一致性解决方案

  • modified 的情况(修改缓存):

当CPU更改了某条cache line中的数据,则该cache line中的数据比对应内存中的数据新,此时需要将这条cache line标记为modified,以便在必要时候(cache满了,该cache line需要被释放,把位子腾出来给新的cache line,置换算法FIFO,LRU…等等)将cache line中的内容flush到内存来同步。

  • invalidate的情况(修改内存):

在使用DMA的时候,外设(比如网卡)过来的数据会不经过CPU直接传送到内存,这时内存中的数据就比对应cache中的数据要新,需要使无效(invalidate)相关的cache line(标记为dirty),这样CPU下次读取这条cache line里的数据的时候,才能知道这些数据不是最新的,得从内存更新。

为了解决多核情况下的缓存一致性问题?

CPU从内存中读取数据到cache line的操作被称为"load",
而将cache line中的数据写回到内存对应位置的操作则被称为"store"。
在硬件设计中,cache属于CPU的一部分,而CPU和内存之间是通过bus(总线)相连的,因此不管是"load"还是"store"操作,都需要经过总线的传输,总线的一次传输被称为"transcation"。

在这里插入图片描述
为了解决这个缓存不一致的问题,我们就需要有一种机制:

  • 写传播(Write Propagation): 写传播是说,在一个CPU核心里,我们的Cache数据更新,必须能够传播到其他的对应节点的Cache Line里。
  • 事务的串行化(Transaction Serialization): 事务串行化是说,我们在一个CPU核心里面的读取和写入,在其他的节点看起来,顺序是一样的。

多个cpu 对同一个数据进行修改时,怎么保证其他cpu节点看到的数据变化顺序是一致的?
在这里插入图片描述

怎样才能看到其他的cache是有变化的?

实现的方式大致说来有两种,一种是 “Snooping” ,一种是 "Directory-based"

  • 广而告之(Bus Snooping总线嗅探)

Snooping机制采用广播的形式,也就是当一个CPU修改了cache line之后,将广播通知到总线上其他所有的CPU。收听广播是需要耳朵(或者收音机)的,对于CPU来说这个“耳朵”就是一个硬件单元(所以应该叫“听到”更合适),它会负责监听总线上所有transactions的广播。

在这里插入图片描述

  • 精准投放(Directory-based)

Directory-based机制采用的是点对点的方式进行传播(就像打电话一样),每个总线transaction只会发给感兴趣的CPU。就是拥有这个transaction所涉及内存位置对应的cache line。所有CPU的cache line的信息,都被记录在这个directory(电话簿)里。

在这里插入图片描述

  • 写失效协议

MESI协议,是一种叫作 写失效(Write Invalidate)的协议 。在写失效协议里:
1、只有一个CPU核心负责写入数据,
2、其他的核心,只是同步读取到这个写入。在这个CPU核心写入Cache之后,它会去广播一个“失效”请求告诉所有其他的CPU核心。
3、其他的CPU核心,只是去判断自己是否也有一个“失效”版本的CacheBlock,然后把这个也标记成失效的就好了。

  • 写广播(Write Broadcast)

1、一个写入请求广播到所有的CPU核心,同时更新各个核心里的Cache。写广播在实现上自然很简单。
2、但是写广播需要占用更多的总线带宽。
写失效只需要告诉其他的CPU核心,哪一个内存地址的缓存失效了,但是写广播还需要把对应的数据传输给其他CPU核心

MSI 协议:如何保证串行化Transaction 和 写传播

“M”, “S”, "I"这3个字母代表了一个cache line可能的三种状态,分别是 Modified, Shared 和 Invalid

  1. 处于Shared状态: 当一些CPU从内存读取了数据到自己的cache line,此时这些CPU中的这些cache line中的数据都是一样的,和内存对应位置的数据也是一样的,cache line都处于shared状态
    在这里插入图片描述
  2. Shared修改成Modified : P2将自己cache line的数据更改为13,P2的这条cache line就变为modified状态(S–>M),其他CPU的cache line就变为invalid状态(S–>I),其他缓存行就变成了Invalid

在这里插入图片描述

  1. 读取Invalid,触发Cache Miss (read Miss),Modified 触发 wirteBack 写回到内存:
    P1试图读取这条cache line中的数据,由于是invalid状态,于是将触发cache miss(细分的话叫read miss),那么P2将会把自己cache line的数据写回(writeback)到内存,供P1从内存读取,之后P1和P2的cache line都将回到shared状态(I–>S, M–>S)
    在这里插入图片描述
  2. 修改Invalid(多个cpu 同时写cache,会有lock前缀锁住缓存行,保证串行执行),触发Cache Miss(Write Miss),会将其他行设置成Invalid : 而是写入这条cache line,那么也将触发cache miss(不过这次是write miss),P1的cache line将变为modified状态(I–>M),而P2的cache line将变为invalid状态(M–>I)
    在这里插入图片描述
  3. DMA 直接写入内存,所有缓存行都失效:
    在这里插入图片描述

这样看来的MESI是可以保证缓存一致性,也间接保证了可见性,那为什么需要volatile 关键字来保证可见性?

先来看MESI存在的问题:

  1. 伪缓存问题:因为是针对缓存行嘛,缓存行可以存64字节,就存在x,y两个数据同时存在缓存行上面 (情景:线程1 只需要x值,线程2只需要y值)
    在这里插入图片描述
    某一“缓存行”中的多个变量被多个线程共享,其中线程1对变量X的值进行了修改,这时即使在另一个CPU内核工作的线程2没有对变量y进行修改,后者的“缓存行”也会被标记为“I”,当线程2要对变量y的值进行读取的时候,需要重新从内存中读取。但此时y的值是没有变化的。

多个CPU内核抢占同一缓存行上的不相关变量所引起的“活锁”情况,称之为伪共享。在高并发情况下,这种MESI协议引起的“活锁”情况反而降低了整个系统的性能

解决方案: 就是将对象x补充到64个字节,让对象x独享一个缓存行。

  1. 性能问题: 修改的时候需要同步等到其他cpu的 Invalid Ack,产生了同步阻塞

在某个CPU尝试修改在其它CPU Cache Line中存在的数据时,性能表现非常糟糕,因为它需要发出Invalidate消息并等待其他cpu 的Ack回来,但是这个事情可能不需要同步,跟下面的没有因果关系, 不值得阻塞等待,比如执行写入的CPU可能只是简单的给这个地址赋值(而不关心它的当前值是什么)。

在这里插入图片描述

  • cpu1修改数据,发起Valid 消息,同步阻塞等待其他cpu相应 Invalid ack,cp2,cp3 利用嗅探机制进行监听。
    在这里插入图片描述
  • cpu2,cp3 将cache 设置成Invalid,并向总线返回invalid ack,cp1 收到 invalid ack,将cache 设置为 Exclusive。
    在这里插入图片描述
  • CPU1 将数据写入cache 并将Exclusive状态改为modified 状态
    在这里插入图片描述
  • cpu2想要获取x值,x值在cpu1 modified cache,cpu1将x值传给cpu2,将x值写回内存,并将cache 设置为shared
    在这里插入图片描述
    cache modified 数据写回内存的时机:
  • 所在cpu的cache 不够,发生了cache置换
  • 其他cpu需要获取被更改的数值

volatile 靠的是MESI协议解决可见性问题?(下)

参考资料:cache之多核一致性(二) - MSI协议

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值