c++11中的内存建模测试,对memory_order_relaxed很好奇

memory modeling test in c++11 , curious for memory_order_relaxed

本文关键字:memory order relaxed 好奇 内存 建模 测试 c++11      更新时间:2023-10-16

我已经阅读了网页:

http://bartoszmilewski.com/2008/12/01/c-atomics-and-memory-ordering/

然后对g++4.8.1编译的测试源进行编码,cpu为英特尔。。。

global var : r1=0;r2=0;x=0;y=0;
Thread1 : 
         x  = 1 ;  //line 1 
         r1 = y ;  //line 2
Thread2 :
         y  = 1 ;  //line 3 
         r2 = x ;  //line 4

并且我将得到r1==0&r2==0,有时同时运行线程1和线程2,我知道这是之前执行的y(第2行)和x(第4行)的负载存储x(第1行),存储y(第3行)。。。。即使是像intel cpu这样的强内存模型,存储之前的负载紊乱仍然发生,这就是为什么r1==0&r2==0仍然发生在这次测试中!!!!

参考c++11内存模型,我更改了如下来源:

global vars :
             int r1=0,r2=0 ;
             atomic<int> x{0} ;
             atomic<int> y{0} ; 
Thread1 :
             x.store(1,memory_order_acq_rel) ;
             r1=y.load(memory_order_relaxed) ;
Thread2 :
             y.store(1,memory_order_acq_rel) ;
             r2=x.load(memory_order_relaxed) ;

这一次,没有r1==0&amp;r2==0发生时,我使用的内存顺序到我一开始提到的网站,请参阅声明:

memory_order_aquire:确保后续加载不会在当前加载或任何先前加载之前移动。

memory_order_release:前面的存储不会移过当前存储或任何后续存储。

memory_order_aq_rel:结合前面两个保证

memory_order_relaxed:所有重新排序都可以。

看看工作。。。我仍然做另一个测试,我把代码改为:

global vars :
             int r1=0,r2=0 ;
             atomic<int> x{0} ;
             atomic<int> y{0} ; 
Thread1 :
             x.store(1,memory_order_relaxed) ;
             r1=y.load(memory_order_relaxed) ;
Thread2 :
             y.store(1,memory_order_relaxed) ;
             r2=x.load(memory_order_relaxed) ;

让我困惑的是,这个测试仍然没有得到r1==0&amp;r2==0!!如果这种情况有效,为什么要使用memoryorderaq_rel呢?或者这只起作用在英特尔cpu?其他类型的cpu仍然需要x和y的存储中的memoryorderaq_rel?

你的第一个实验的结果很有趣:"当同时运行thread1和thread2时,我有时会得到r1==0&r2==0……即使是像intel cpu这样的强内存模型,在存储之前的负载也会紊乱",但这不仅仅是因为你认为的原因。Atomics不仅阻止处理器和缓存子系统重新排序内存访问,还阻止编译器。Coliru的GCC 4.8优化了此代码,以便在存储之前使用加载指令进行组装:

_Z7thread1v:
.LFB326:
    .cfi_startproc
    movl    y(%rip), %eax
    movl    $1, x(%rip)
    movl    %eax, r1(%rip)
    ret

即使处理器保证了这里的内存顺序,你也需要一些围栏来防止编译器把事情搞砸。

由于使用memory_order_acq_rel作为store的内存排序,您的第二个程序格式不正确。acquire仅对加载有意义,release仅对存储有意义,因此memory_order_acq_rel仅作为原子读-修改-写操作(如exchangefetch_add)的排序有效。用memory_order_release替换m_o_a_r实现了您想要的语义,并且生成的程序集再次令人感兴趣:

_Z7thread1v:
.LFB332:
    .cfi_startproc
    movl    $1, x(%rip)
    movl    y(%rip), %eax
    movl    %eax, r1(%rip)
    ret

这些指令正是我们期望生成的,没有特殊的围栏指令。处理器内存模型足够强大,可以为普通的旧mov指令提供必要的排序保证。在这种情况下,原子只需要告诉编译器不要插手代码。

尽管生成了与第二个程序相同的程序集,但第三个程序(技术上)是不可预测的:

_Z7thread1v:
.LFB332:
    .cfi_startproc
    movl    $1, x(%rip)
    movl    y(%rip), %eax
    movl    %eax, r1(%rip)
    ret

虽然这次的结果是一样的,但不能保证编译器不会像第一个程序那样选择重新排序指令。当您升级编译器、引入其他指令或出于任何其他原因时,结果可能会发生变化。如果你开始在ARM上编译,所有的赌注都会被取消;)同样有趣的是,尽管放宽了源程序中的要求,但生成的汇编程序是相同的。在处理器体系结构的限制之外,没有办法放松内存排序。

这里有很多问题:(1) 发布和获取必须成对。否则,它们不会建立同步,也不会保证任何东西。(2) 即使您在示例中释放存储并获取负载,内存模型仍然允许r1=r2=0。您需要将所有内容设置为seq_cst以禁止执行。(3) 我们在http://demsky.eecs.uci.edu/c11modelchecker.html用于测试C11原子代码。它将为您提供在合理解释C/C++11内存模型的情况下允许的所有执行。

您可能还没有在当前GCC版本上看到这些有趣的行为,因为至少早期版本忽略了内存排序参数,并始终使用seq_cst。如果GCC改变了这一点,您可以看到r1=r2=0。