内存次序之内存栅栏

内存栅栏

内存栅栏其实就是强制施加一定的内存次序,又不用更改数据,又被成为内存卡、内存屏障,其实就是相当于在代码中画出界限,规定一些操作的同步关系。

两个规则
  1. 首先是内存次序位释放的栅栏也就是 s t d : : a t o m i c _ t h r e a d _ f e n c e ( s t d : : m e m o r y _ o r d e r _ r e l e a s e ) ; std::atomic\_thread\_fence(std::memory\_order\_release); std::atomic_thread_fence(std::memory_order_release);,这个如果一个数的存储操作在这个释放栅栏的后面,并且这个存储操作的值被后面获取(这个获取操作意思是内存次序为获取,也就是 s t d : : m e m o r y _ o r d e r _ a c q u i r e std::memory\_order\_acquire std::memory_order_acquire,不是指load操作)操作给看见了,ok,这个释放栅栏就和后面的获取操作同步了。这里的存储操作不需要规定特定的内存次序,甚至不是原子类型都可以。
  2. 还有就是内存次序为获取的栅栏也就是 s t d : : a t o m i c _ t h r e a d _ f e n c e ( s t d : : m e m o r y _ o r d e r _ a c q u i r e ) ; std::atomic\_thread\_fence(std::memory\_order\_acquire); std::atomic_thread_fence(std::memory_order_acquire); 如果一个数的载入操作在获取栅栏的前面,并且载入操作看到了释放(这个释放操作的意思是内存次序为释放,也就是 s t d : : m e m o r y _ o r d e r _ r e l e a s e std::memory\_order\_release std::memory_order_release,而不是指store操作)操作的结果,那么这个获取栅栏和释放操作同步,这里的载入操作不需要规定特定的内存次序,甚至不是原子类型都可以。
具体例子

接下来我们看一个例子,如下代码,

  • 这里可以看出来操作3是y的存储操作,并且在释放栅栏的后面,也就满足了第一个规则,他会和能看见y存储操作的值的并且内存次序为获取的操作同步,(句子优点绕,但是仔细看看能明白),先记住这个点,一会我们会用;
  • 我们继续看下面代码,操作4是y的载入操作,并且在获取栅栏的前面,并且它还看到了载入操作的结果,也就满足了第二个规则,他会和对y存储该载入值的释放操作同步,(也有点绕,我解释一下,就是载入操作载入的值,是被哪个释放操作写入的);

ok,我们分析完下面的代码满足的内存栅栏的规则,我们接下面分析同步关系,根据上面的第一个分析,也就是下述代码2的释放栅栏会和能看见y存储操作的值的并且内存次序为获取的操作同步,但是我们可以看到操作4,y的载入的内存次序并不是获取啊,没关系,我们要求的只是能看见y存储操作的值都获取操作,因此我们不必拘于操作4,操作5也是能看见y存储操作的值的,因为操作4是死循环,看不见就不停止,因此我们可以得出操作2和操作5是同步的,没毛病吧。

还没完,我们继续看第二个分析,也就是下述代码操作5,会和y存储该载入值的释放操作同步,但是看完代码我们还是可以发现,y的存储操作3的内存次序也不是释放啊,没关系,我们向前看,操作3前面就是释放操作,那么获取栅栏5也就和释放栅栏2,同步了,没毛病吧,齐活,相互同步,两个内存栅栏也就同步了,因此下面代码的断言不可能触发(2同步与5,1先行与2,5先行与6,1先行与6)。这里强调一点嗷,这里是内存栅栏之间的同步,不是内存栅栏和存储、载入操作的同步嗷。为了解释这个关键点,我们看下面的例子:

如果我们把内存栅栏2,放到0的位置,那么就不能确保断言一定不会触发了,根据上面的分析,0还是会和5同步,这翁庸质疑,但是这里先行关系不再存在,也就是操作1并不一定先行与5,因为5并不一定能看到x存储的值,5只能确保看到y存储的值,因此断言是可能触发的。

#include 
#include 
#include 
std::atomic<bool> x,y;
std::atomic<int> z;
void write_x_then_y()
{
    /*栅栏放这里就不行了 在这里的话 这一线程x和y是有先行关系的
    其他线程就没了*/
    //std::atomic_thread_fence(std::memory_order_release);//0
    x.store(true,std::memory_order_relaxed);//1
    std::atomic_thread_fence(std::memory_order_release);//2
    y.store(true,std::memory_order_relaxed);//3
}

void read_x_then_y()
{
    while(!y.load(std::memory_order_relaxed));//4
    std::atomic_thread_fence(std::memory_order_acquire);//5
    if(x.load(std::memory_order_relaxed))//6
        ++z;
}
int  main()
{
    x = false;
    y = false;
    z = 0;
    std::thread a(write_x_then_y);
    std::thread b(read_x_then_y);
    a.join();
    b.join();
    assert(z.load() != 0);
    return 0;
}
栅栏对非原子数据类型依然适用

栅栏对非原子数据类型的内存次序限制,按照上面的分析我们仍然可以分析出来,1先行与2,2同步与5,5先行与6 ,因此1先行与6,断言不会触发。因此内存栅栏还可以对非原子类型的内存次序进行限制。其实不止内存栅栏可以限制非原子数据类型的内存次序,内存次序为释放和消耗的数据也可以限制非原子数据类型的内存次序,名为数据依赖,这里就不细说了,有时间单独说。

#include 
#include 
#include 
bool x = false;
std::atomic<bool> y;
std::atomic<int> z;
void write_x_then_y()
{
    x = true;//1
    std::atomic_thread_fence(std::memory_order_release);//2
    y.store(true,std::memory_order_relaxed);//3
}

void read_x_then_y()
{
    while(!y.load(std::memory_order_relaxed));//4
    std::atomic_thread_fence(std::memory_order_acquire);//5
    if(x)//6
        ++z;
}
int  main()
{
    x = false;
    y = false;
    z = 0;
    std::thread a(write_x_then_y);
    std::thread b(read_x_then_y);
    a.join();
    b.join();
    assert(z.load() != 0);
    return 0;
}

你可能感兴趣的:(多线程c++,c++)