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

标签 c++ multithreading c++11 memory-model stdatomic

我已阅读网页:

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 , 我知道是之前执行的y的加载(第2行)和x的加载(第4行) x 的存储(第 1 行),y 的存储(第 3 行)....甚至像英特尔 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 && 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?

最佳答案

你的第一个实验的结果很有趣:“我有时会得到 r1==0 && r2 == 0 同时运行 thread1 和 thread2 ....即使是像 intel cpu 这样的强内存模型,在存储之前负载困惑发生”,但不仅仅是出于您认为的原因。原子不仅阻止处理器和缓存子系统重新排序内存访问,而且 编译器 也是如此。 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 仅作为原子读取-修改-写入操作的顺序有效像 exchangefetch_add。将 m_o_a_r 替换为 memory_order_release 可实现您想要的语义,assembly produced is again interesting :

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

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

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

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

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

关于c++ - c++11 中的内存建模测试,对 memory_order_relaxed 感到好奇,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17959814/

相关文章:

c++ - 有没有办法编写一个将 l-val ref 或 r-val reference 作为输入参数的函数?

C++,如何创建和绘制二叉树然后在预购中遍历它

c - 多线程程序中计数意外增加

带有 OpenMP 线程安全随机数的 C++

python - 使用多处理时实例变量未更新 Python

c++ - 视觉 C++ : forward an array as a pointer

c++ - SFINAE 无法有条件地编译成员函数模板

c++ - 通过 Lambda 删除 vector 项

c++ - 与当前 OpenGL 上下文对应的唯一 OpenCL 上下文