我已阅读网页:
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
仅作为原子读取-修改-写入操作的顺序有效像 exchange
或 fetch_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/