0

我已阅读网页:

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

而且我有时会在同时运行thread1和thread2时得到r1==0 && r2 == 0,我知道这是在存储x(第1行)之前执行的y(第2行)和x(第4行)的负载, store of y(line 3) ....即使像 intel cpu 这样的强内存模型,在 store 之前加载无序仍然会发生,这就是为什么 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 && 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 && r2==0 的结果!如果这种情况有效,为什么还要使用 memory_order_acq_rel ?或者这只适用于英特尔CPU?其他类型的 cpu 在 x 和 y 的存储中仍然需要 memory_order_acq_rel 吗?

4

2 回答 2

2

你的第一个实验的结果很有趣:“我有时会在同时运行 thread1 和 thread2 时得到 r1==0 && r2 == 0 ....即使是像 intel cpu 这样的强内存模型,在存储之前加载无序仍然发生”但是不仅仅是因为你认为的原因。原子不仅阻止处理器和缓存子系统重新排序内存访问,而且还阻止编译器Coliru 的 GCC 4.8将此代码优化为在存储之前使用加载指令进行汇编:

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

即使处理器在这里保证了内存排序,您也需要某种围栏来防止编译器搞砸事情。

您的第二个程序格式不正确,因为它使用memory_order_acq_relstore. acquire仅对加载有意义,并且release仅对存储有意义,因此memory_order_acq_rel仅作为诸如exchangeor之类的原子读-修改-写操作的排序有效fetch_add。替换m_o_a_rmemory_order_release您想要的语义,并且生成的程序集再次有趣

_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 上编译,所有的赌注都没有了 ;) 同样有趣的是,尽管放宽了源程序中的要求,但生成的汇编程序是相同的。在处理器架构设置的限制之外,没有办法放松内存排序。

于 2013-07-31T02:17:36.017 回答
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。

于 2013-09-02T04:53:03.443 回答