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

时间:2013-07-31 00:47:46

标签: c++11 concurrency atomic

我已阅读网页:

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

然后编译在g ++ 4.8.1编译的测试源,cpu是Intel ...

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有时同时运行thread1和thread2, 我知道之前执行的是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;&amp; r2 == 0发生,我使用的memory_order是相应的 到我在开始时提到的网站,请参阅声明:

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

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

memory_order_acq_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;&amp;的结果r2 == 0 !! 如果这种情况有效,为什么还要使用memory_order_acq_rel呢?或者这只有效 在intel cpu?其他类型的cpu在x和y的存储中仍然需要memory_order_acq_rel吗?

2 个答案:

答案 0 :(得分:2)

你的第一个实验的结果很有意思:“有时候我会得到r1 == 0&amp;&amp; r2 == 0同时运行thread1和thread2 ....甚至强大的内存模型,如intel cpu,加载无序在商店仍然发生之前“但不仅仅是因为你认为的原因。 Atomics不仅可以防止处理器和缓存子系统重新排序内存访问,还可以阻止编译器GCC 4.8 at Coliru在商店之前优化此代码以使用加载指令进行汇编:

_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仅作为原子读 - 修改 - 写操作的排序有效,如exchange或{{ 1}}。用fetch_add替换m_o_a_r可以实现您想要的语义,以及assembly produced is again interesting

memory_order_release

指令正是我们期望生成的,没有特殊的栅栏指令。处理器内存模型足够强大,可以使用普通的_Z7thread1v: .LFB332: .cfi_startproc movl $1, x(%rip) movl y(%rip), %eax movl %eax, r1(%rip) ret 指令提供必要的排序保证。在这种情况下,原子只需要告诉编译器将其手指放在代码之外。

您的第三个程序(技术上)不可预测despite generating the same assembly as the second

mov

虽然这次结果是相同的,但不能保证编译器不会像第一个程序那样选择重新排序指令。升级编译器或引入其他指令或任何其他原因时,结果可能会更改。如果你开始在ARM上编译,那么所有的赌注都会关闭;)尽管放宽了源程序中的要求,生成的汇编程序也是一样的,这也很有趣。除了处理器架构所采用的限制之外,没有办法放松内存排序。

答案 1 :(得分:1)

这里有很多问题: (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。