我已阅读网页:
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吗?
答案 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。