lxy*_*cls 3 c++ atomic lock-free memory-barriers c++11
我的测试代码如下,我发现只有memory_order_seq_cstforbade编译器重新排序.
#include <atomic>
using namespace std;
int A, B = 1;
void func(void) {
A = B + 1;
atomic_thread_fence(memory_order_seq_cst);
B = 0;
}
Run Code Online (Sandbox Code Playgroud)
而其他选择memory_order_release,memory_order_acq_rel根本没有产生任何编译屏障.
我认为他们必须使用原子变量,如下所示.
#include <atomic>
using namespace std;
atomic<int> A(0);
int B = 1;
void func(void) {
A.store(B+1, memory_order_release);
B = 0;
}
Run Code Online (Sandbox Code Playgroud)
但我不想使用原子变量.与此同时,我认为"asm("":::"记忆")"太低了.
还有更好的选择吗?
re:你的编辑:
但我不想使用原子变量.
为什么不?如果是出于性能原因,请使用memory_order_relaxed和atomic_signal_fence(mo_whatever)来阻止编译器重新排序,除了编译器屏障可能会阻止某些编译时优化,而不是任何运行时开销,具体取决于周围的代码.
如果出于某种其他原因,那么可能atomic_signal_fence会为您提供恰好在您的目标平台上运行的代码.我怀疑它确实订购了非atomic<>加载和/或存储,因此它甚至可以帮助避免C++中的数据争用Undefined Behavior.
什么足够?
无论有任何障碍,如果两个线程同时运行此函数,则由于对非atomic<>变量的并发访问,您的程序具有未定义的行为.因此,如果您正在讨论与在同一线程中运行的信号处理程序同步,则此代码有用的唯一方法.
这也与要求"编译器障碍"一致,只是为了防止在编译时重新排序,因为无序执行和内存重新排序总是保留单个线程的行为.因此,您永远不需要额外的屏障指令来确保按程序顺序查看自己的操作,您只需要在编译时停止编译器重新排序.请参阅Jeff Preshing的帖子:编译时的内存排序
这atomic_signal_fence是为了什么.您可以将它与任何一个一起使用std::memory_order,就像thread_fence一样,可以获得不同的屏障强度,并且只能阻止您需要防止的优化.
...
atomic_thread_fence(memory_order_acq_rel)根本没有产生任何编译器障碍!
在几个方面完全错了.
atomic_thread_fence 是一个编译器障碍加上任何运行时障碍是必要的限制重新排序的顺序我们的加载/存储变得对其他线程可见.
我猜你的意思是当你查看x86的asm输出时它没有发出任何障碍指令.像x86的MFENCE这样的指令不是"编译器障碍",它们是运行时内存屏障,甚至阻止StoreLoad在运行时重新排序.(这是x86允许的唯一重新排序.只有在使用弱排序(NT)存储时才需要SFENCE和LFENCE,如MOVNTPS(_mm_stream_ps).)
在像ARM这样弱有序的ISA上,thread_fence(mo_acq_rel)不是免费的,而是编译成一条指令.gcc5.4使用dmb ish.(在Godbolt编译器资源管理器中查看).
编译器屏障只是在编译时阻止重新排序,而不必阻止运行时重新排序.所以即使在ARM上,atomic_signal_fence(mo_seq_cst)编译也没有指令.
一个足够弱的屏障允许编译器在商店B之前进行存储,A如果它想要,但gcc恰好决定仍然按源顺序执行它们,即使使用thread_fence(mo_acquire)(不应该与其他商店订购商店) .
所以这个例子并没有真正测试某些东西是否是编译器障碍.
来自gcc的奇怪编译器行为与编译器障碍不同的示例:
#include <atomic>
using namespace std;
int A,B;
void foo() {
A = 0;
atomic_thread_fence(memory_order_release);
B = 1;
//asm volatile(""::: "memory");
//atomic_signal_fence(memory_order_release);
atomic_thread_fence(memory_order_release);
A = 2;
}
Run Code Online (Sandbox Code Playgroud)
这会以你期望的方式编译clang:thread_fence是StoreStore屏障,因此A = 0必须在B = 1之前发生,并且不能与A = 2合并.
# clang3.9 -O3
mov dword ptr [rip + A], 0
mov dword ptr [rip + B], 1
mov dword ptr [rip + A], 2
ret
Run Code Online (Sandbox Code Playgroud)
但是对于gcc,屏障没有效果,只有A的最终存储在asm输出中.
# gcc6.2 -O3
mov DWORD PTR B[rip], 1
mov DWORD PTR A[rip], 2
ret
Run Code Online (Sandbox Code Playgroud)
但是atomic_signal_fence(memory_order_release),gcc的输出匹配clang. 因此atomic_signal_fence(mo_release)具有我们期望的屏障效果,但是atomic_thread_fence任何弱于seq_cst的东西都不会充当编译屏障.
这里的一个理论是gcc知道它是多个线程写入非atomic<>变量的正式Undefined Behavior .这并没有多少水,因为atomic_thread_fence如果用于与信号处理程序同步仍然可以工作,它只是比必要的更强.
顺便说一下atomic_thread_fence(memory_order_seq_cst),我们得到了预期
# gcc6.2 -O3, with a mo_seq_cst barrier
mov DWORD PTR A[rip], 0
mov DWORD PTR B[rip], 1
mfence
mov DWORD PTR A[rip], 2
ret
Run Code Online (Sandbox Code Playgroud)
即使只有一个屏障,我们也可以得到这个,这仍然允许A = 0和A = 2存储一个接一个地发生,因此允许编译器跨屏障合并它们.(观察者未能看到单独的A = 0和A = 2值是可能的排序,因此编译器可以决定总是会发生什么).但是,当前的编译器通常不会进行这种优化.请参阅我的答案结尾处的讨论Can num ++是'int num'的原子?.