美文网首页
volatile与内存屏障

volatile与内存屏障

作者: 桭宝 | 来源:发表于2021-01-25 17:16 被阅读0次

    上一篇:volatile与缓存一致性

    volatile的作用

    1.保证变量的可见性

    2.局部限制指令重排(指令重排存在多种情况:1.编译器重排;2.处理器并行重排;3.因为store buffer,invalid queue等异步机制的存在导致的内存重排)

    为了达到这两个目的,volatile做了两件事情:

    1.禁止编译器的优化和重排

    2.通过内存屏障限制处理器重排

    下面简单梳理下volatile如何限制编译器和处理器的重排。

    首先明确下排序规则,简单总结如下(参考JSR-133

    a.第二个操作为volatile写,则无论第一个操作是什么,都无法重排序;

    b.第一个操作为volatile读,则无论第二个操作是什么,都无法重排序;

    c.第一个操作为volatile写,第二个操作为volatile读,无法重排序;

    如何限制编译器指令重排

    规则c保证了volatile变量自身的可见性,abc一起保证了操作的局部有序以及一些跨线程数据依赖的正确性。在某些情况下,不仅仅要求volatile变量本身的可见性,也需要有序性来保证语义的正确,如下面的典型场景:

    两个变量:

    int x=0;

    int y=0;

    线程A执行:

    y=1     //1

    x=1     //2

    线程B执行:

    while(x==1) {      //3

        assert y==1;   //4

    }

    按照程序的语义,最后的断言应该是通过,但实际情况并非如此。原因有两个:1.如果没有a限制,操作1和操作2之前可能会被重排序,操作3的条件成立时,操作1可能还未执行,导致断言失败;2.编译器会对线程B的代码做优化,因为编译器无法判断跨线程的数据依赖,单独从线程B的视角看,x是不会变化的,为了提升性能,while(x==1)为直接被替换为while(false),永远无法执行操作4。

    解决方案就是将变量x声明为volatile,限制的编译器的优化和操作1、操作2的重排,同时保证操作2的结果对操作3立即可见。

    通过内存屏障限制处理器重排

    四类内存屏障:LoadLoad、StoreStore、StoreLoad、LoadStore

    volatile的内存屏障策略:

    volatile写之前插入StoreStore屏障;(规则a,防止重排)

    volatile写之后插入StoreLoad屏障;(规则c,保障可见性)

    volatile读之后插入LoadStore屏障;(规则b,防止重排)

    volatile读之后插入LoadLoad屏障;(规则b,防止重排)

    在补充一些相关知识点,可能比较混乱,但个人觉得有助于对内存屏障的理解

    1.StoreLoad的主要目的是保证volatile变量自身写后读的可见性,在实现上开销最大。

    2.内存屏障作为一种逻辑抽象,具体实现有多种,比如x86下有mfence和lock指令,JVM选择了lock,及在写volatile变量时锁缓存总线,并且让其他CPU上对应缓存行的数据失效。

    3.x86下只有StoreLoad屏障是有效操作,其他的屏障均为no-op。主要原因有二:1. x86中store-buffer是一个FIFO队列,结构上保证了写入顺序;2. x86中没有invalid queue,因此不需要LoadLoad或LoadStore来强制消费失效队列。

    相关文章

      网友评论

          本文标题:volatile与内存屏障

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