Volatile和MESI

作者: 进击的蚂蚁zzzliu | 来源:发表于2021-01-28 19:02 被阅读0次

    概述

    谈到并发编程就离不开可见性/原子性/顺序性的问题,那么产生这几个问题的原因是什么呢?
    一个程序的运行一般都需要CPU、内存、I/O设备的参与,但是三者之间的速度差异巨大,为了平衡这三者的速度差异:

    • 计算机体系结构为CPU增加了缓存,以均衡CPU与内存的速度差异;但是同时也带来了可见性问题;
    • 操作系统采用分时复用技术,以均衡CPU与I/O设备的速度差异;但是同时也带来了原子性问题
    • 编译器优化指令执行次序,使得缓存能够得到更加合理地利用;但是同时也带来了有序性问题

    本节先来分析CPU缓存导致的缓存一致性问题

    1、总线嗅探

    总线嗅探本质上就是把所有的读写请求都通过总线广播给所有的 CPU 核心,然后让各个核心去“嗅探”这些请求,再根据本地的情况进行响应;

    2、MESI 协议

    MESI 协议是基于总线嗅探机制的一种缓存一致性协议,在多处理器中为了保证各个处理器的缓存一致性,每个处理器通过嗅探在总线上传播的数据来检查自己的缓存值是不是过期了,如果处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置为无效状态,当这个处理器对数据进行修改操作时,会重新从系统内存中读取数据到CPU缓存中;

    2.1 MESI 含义

    M:代表已修改(Modified):Cache Block 里面的内容我们已经更新过了,但是还没有写回到主内存里面;
    E:代表独占(Exclusive):Cache Block 里面的数据和主内存里面的数据是一致的;
    S:代表共享(Shared):Cache Block 里面的数据和主内存里面的数据是一致的;
    I:代表已失效(Invalidated):Cache Block 里面的数据已经失效了,不可以相信这个 Cache Block 里面的数据;
    重点是E和S,
    1.在独占状态下,对应的 Cache Line 只加载到了当前 CPU 核所拥有的 Cache 里。其他的 CPU 核,并没有加载对应的数据到自己的 Cache 里。这个时候,如果要向独占的 Cache Block 写入数据,我们可以自由地写入数据,而不需要告知其他 CPU 核;
    2.在独占状态下的数据,如果收到了一个来自于总线的读取对应缓存的请求,它就会变成共享状态。这个共享状态是因为,这个时候,另外一个 CPU 核心,也把对应的 Cache Block,从内存里面加载到了自己的 Cache 里来;
    3.共享状态下,因为同样的数据在多个 CPU 核心的 Cache 里都有。所以,当我们想要更新 Cache 里面的数据的时候,不能直接修改,而是要先向所有的其他 CPU 核心广播一个请求,其他 CPU 核心嗅探到请求后,先把里面的 Cache都变成无效的状态然后返回响应,之后再更新当前 Cache 里面的数据;

    MESI状态机.png
    2.2 MESI 产生的问题

    CPU操作分为两种:load(读)、store(写),加入缓存的目的是提前缓存内存的数据,提高load的效率,但是store的速度降低了,因为CPU将数据store到内存多了写缓存的步骤,并且需要同步所有CPU的私有缓存。这样store操作会严重阻塞后续的load操作,这样加缓存的意义完全就没有了,不仅没能提高load的效率,反而阻塞了load。为了解决load被阻塞的问题,在CPU中加入了新的组件store buffer(写队列);

    CPU缓存.png
    每个CPU都有一个store buffer,当CPU需要执行store操作时,会将store操作先放入store buffer中,不会立即执行store操作,等待合适的时机再执行(store buffer满了等等情况会真正执行store操作),在store后面的load操作不用再等store真正执行完毕才能执行,只要store放入了store buffer中,load就可以执行了。

    3、Volatile原理

    java的内存屏障通常所谓的四种即LoadLoad(LL),StoreStore(SS),LoadStore(LS),StoreLoad(SL)实际上也是上述两种的组合,完成一系列的屏障和数据同步功能。
    1.volatile变量执行写操作,会在写之前插入StoreStore Barriers,在执行volatile变量写之前的所有Store操作都已执行,数据同步到了内存中(将store buffer中的store操作刷新到内存);在写之后插入StoreLoad Barriers,代表该volatile变量的写操作也会立即刷新到内存中,其他线程会看到最新值;
    2.volatile变量执行读操作,会在读之前插入LoadLoad Barriers,代表在执行volatile变量读之前的所有Load从内存中获取最新值;在读之后插入LoadStore Barriers,代表该读取volatile变量获得是内存中最新的值;

    小结

    MESI协议的引入会导致缓存的写入效率降低,所以引入了store buffer等部件,store buffer将store操作缓存起来,不会立即写入缓存,导致多CPU内的值同步会有一定延迟,间接导致cpu的操作重排序,多cpu的共享变量的操作会发生混乱,所以JMM中可以使用volatile强制刷新store buffer,让多CPU中的值同步没有延迟,保证多CPU共享变量不会发生混乱。

    相关文章

      网友评论

        本文标题:Volatile和MESI

        本文链接:https://www.haomeiwen.com/subject/rrottltx.html