回复:您的编辑:
但我不想使用原子变量。
为什么不?如果出于性能原因,请将它们与memory_order_relaxed
and atomic_signal_fence(mo_whatever)
阻止编译器重新排序,除了编译器屏障之外没有任何运行时开销,可能会阻止某些编译时优化,具体取决于周围的代码。
如果是因为其他原因,那么也许atomic_signal_fence
将为您提供恰好在您的目标平台上运行的代码。我怀疑它的大多数实现确实订购了非atomic<>
在实践中加载和存储,至少作为实现细节,并且如果可以访问,则可能有效地需要atomic<>
变量。因此,在实践中可能有助于避免仍然存在的任何数据争用未定义行为的一些实际后果。 (例如,作为 SeqLock 实现的一部分,为了提高效率,您希望使用共享数据的非原子读/写,以便编译器可以使用 SIMD 向量副本。)
See 谁害怕一个糟糕的优化编译器? https://lwn.net/Articles/793253/在 LWN 上,了解如果您仅使用编译器屏障强制重新加载非atomic
变量,而不是使用具有只读一次语义的东西。 (在那篇文章中,他们谈论的是 Linux 内核代码,因此他们使用volatile
用于手动加载/存储原子。但一般情况下不要这样做:何时在多线程中使用 易失性? https://stackoverflow.com/questions/4557979/when-to-use-volatile-with-multi-threading/58535118#58535118- 几乎从来没有)
够什么用?
不管有什么障碍,如果两个线程同时运行这个函数,你的程序就会有未定义的行为,因为并发访问非atomic<>
变量。因此,此代码唯一有用的方法是,如果您正在谈论与在同一线程中运行的信号处理程序同步。
这也与要求“编译器屏障”一致,仅防止在编译时重新排序,因为乱序执行和内存重新排序始终保留单个线程的行为。因此,您永远不需要额外的屏障指令来确保您按程序顺序看到自己的操作,您只需要在编译时停止编译器重新排序即可。请参阅 Jeff Preshing 的帖子:编译时的内存排序 http://preshing.com/20120625/memory-ordering-at-compile-time/
这是什么atomic_signal_fence http://en.cppreference.com/w/cpp/atomic/atomic_signal_fence is for。您可以将它与任何std::memory_order
,就像 thread_fence 一样,获得不同强度的屏障并仅阻止您需要阻止的优化。
... atomic_thread_fence(memory_order_acq_rel)
根本没有产生任何编译器障碍!
从几个方面来看,这是完全错误的。
atomic_thread_fence
is编译器障碍plus任何运行时障碍都需要限制重新排序,以便我们的加载/存储对其他线程可见。
我猜你的意思是它没有发出任何障碍指示当您查看 x86 的 asm 输出时。像 x86 的 MFENCE 这样的指令不是“编译器障碍”,它们是运行时内存障碍,甚至会阻止运行时的 StoreLoad 重新排序。 (这是 x86 允许的唯一重新排序。仅在使用弱排序 (NT) 存储时才需要 SFENCE 和 LFENCE,例如MOVNTPS (_mm_stream_ps) http://www.felixcloutier.com/x86/MOVNTPS.html.)
在像 ARM 这样的弱有序 ISA 上,thread_fence(mo_acq_rel) 不是免费的,并且会编译为指令。 gcc5.4使用dmb ish
。 (请参阅Godbolt 编译器浏览器 http://gcc.godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(j:1,options:(colouriseAsm:%270%27,compileOnChange:%270%27),source:%27%23include+%3Catomic%3E%0A%0Ausing+namespace+std%3B%0A%0Aint+A,+B+%3D+1%3B%0A%0A//const+std::memory_order+mo+%3D+std::memory_order_seq_cst%3B%0Aconst+std::memory_order+mo+%3D+std::memory_order_acq_rel%3B%0A%0Avoid+func_threadfence(void)+%7B%0A++++A+%3D+B+%2B+1%3B%0A++++atomic_thread_fence(mo)%3B%0A++++B+%3D+0%3B%0A%7D%0A%0A%0Avoid+func_signalfence(void)+%7B%0A++++A+%3D+B+%2B+1%3B%0A++++atomic_signal_fence(mo)%3B%0A++++B+%3D+0%3B%0A%7D%27),l:%275%27,n:%271%27,o:%27C%2B%2B+source+%231%27,t:%270%27)),k:33.92739273927394,l:%274%27,n:%270%27,o:%27%27,s:0,t:%270%27),(g:!((h:compiler,i:(compiler:armhfg54,filters:(b:%270%27,commentOnly:%270%27,directives:%270%27),options:%27-O3+-std%3Dgnu%2B%2B11+-Wall+-Wextra+-fno-verbose-asm%27),l:%275%27,n:%270%27,o:%27%231+with+ARM+gcc+5.4%27,t:%270%27)),k:32.739273927392745,l:%274%27,n:%270%27,o:%27%27,s:0,t:%270%27),(g:!((h:compiler,i:(compiler:g62,filters:(b:%270%27,commentOnly:%270%27,directives:%270%27,intel:%270%27),options:%27-O3+-std%3Dgnu%2B%2B11+-Wall+-Wextra+-fno-verbose-asm%27),l:%275%27,n:%270%27,o:%27%231+with+x86-64+gcc+6.2%27,t:%270%27)),k:33.33333333333333,l:%274%27,n:%270%27,o:%27%27,s:0,t:%270%27)),l:%272%27,n:%270%27,o:%27%27,t:%270%27)),version:4).
编译器屏障只是阻止编译时重新排序,而不一定阻止运行时重新排序。所以即使在 ARM 上,atomic_signal_fence(mo_seq_cst)
编译为无指令。
足够弱的屏障允许编译器进行存储B
到商店之前A
如果它愿意,但 gcc 碰巧决定仍然按源顺序执行它们,即使使用 thread_fence(mo_acquire) (不应该与其他商店一起订购商店)。
所以这个例子并没有真正测试某些东西是否是编译器障碍。
gcc 的奇怪编译器行为与编译器屏障不同的示例:
请参阅 Godbolt 上的源代码+asm http://gcc.godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(j:1,options:(colouriseAsm:%270%27,compileOnChange:%270%27),source:%27%23include+%3Catomic%3E%0Ausing+namespace+std%3B%0Aint+A,B%3B%0A%0Avoid+foo()+%7B%0A++A+%3D+0%3B%0A++atomic_thread_fence(memory_order_release)%3B%0A++B+%3D+1%3B%0A++//asm+volatile(%22%22:::+%22memory%22)%3B%0A++//atomic_signal_fence(memory_order_release)%3B%0A++atomic_thread_fence(memory_order_release)%3B%0A++A+%3D+2%3B%0A%7D%27),l:%275%27,n:%271%27,o:%27C%2B%2B+source+%231%27,t:%270%27)),k:33.69797478330771,l:%274%27,n:%270%27,o:%27%27,s:0,t:%270%27),(g:!((h:compiler,i:(compiler:g62,filters:(b:%270%27,commentOnly:%270%27,directives:%270%27,intel:%270%27),options:%27-O3+-std%3Dc%2B%2B11+-fno-verbose-asm+-Wall+-Wextra%27),l:%275%27,n:%270%27,o:%27%231+with+x86-64+gcc+6.2%27,t:%270%27)),k:32.96869188335898,l:%274%27,n:%270%27,o:%27%27,s:0,t:%270%27),(g:!((h:compiler,i:(compiler:clang390,filters:(b:%270%27,commentOnly:%270%27,directives:%270%27,intel:%270%27),options:%27-O3+-std%3Dc%2B%2B11+-fno-verbose-asm+-Wall+-Wextra%27),l:%275%27,n:%270%27,o:%27%231+with+x86-64+clang+3.9.0%27,t:%270%27)),k:33.33333333333333,l:%274%27,n:%270%27,o:%27%27,s:0,t:%270%27)),l:%272%27,n:%270%27,o:%27%27,t:%270%27)),version:4.
#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;
}
这会按照您期望的方式使用 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
但对于 gcc,屏障没有任何作用,并且只有最终存储到 A 的内容出现在 asm 输出中。
# gcc6.2 -O3
mov DWORD PTR B[rip], 1
mov DWORD PTR A[rip], 2
ret
但与atomic_signal_fence(memory_order_release)
,gcc 的输出与 clang 匹配。So atomic_signal_fence(mo_release)
具有我们预期的屏障效应,但是atomic_thread_fence
任何比 seq_cst 弱的东西根本不会充当编译器障碍。
这里的一种理论是 gcc 知道多线程写入非官方行为是未定义的行为atomic<>
变量。这并不能容纳太多水,因为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
即使只有一个屏障,我们也能得到这一点,这仍然允许 A=0 和 A=2 存储相继发生,因此允许编译器跨屏障合并它们。 (观察者无法看到单独的 A=0 和 A=2 值是一种可能的排序,因此编译器可以决定这总是发生的情况)。不过,当前的编译器通常不会进行这种优化。请参阅我的答案末尾的讨论num++ 对于“int num”可以是原子的吗? https://stackoverflow.com/questions/39393850/can-num-be-atomic-for-int-num/39396999#39396999.