frame
与内核共享。
用户空间代码:
read frame // read frame content
_mm_mfence // prevent before "releasing" a frame before we read everything.
frame.status = 0 // "release" a frame
内核代码:
poll for frame.status // reads a frame's status
_mm_lfence
内核可以在另一个线程中异步轮询它。因此,用户空间代码和内核空间之间没有syscall
。
同步是否正确?
由于以下情况,我对此表示怀疑:
编译器的内存模型很弱,我们必须假定它可以进行疯狂的更改,正如您可以想象的那样,如果优化/更改的程序在单线程内是一致的。
因此,在我看来,我们需要第二个障碍,因为编译器有可能优化store frame.status, 0
。
是的,这将是一个非常疯狂的优化,但是如果编译器能够证明上下文中(线程内)没有人读取该字段,则可以对其进行优化。
我相信这是理论上的可能性,不是吗?
因此,为防止出现这种情况,我们可以设置第二个障碍:
用户空间代码:
read frame // read frame content
_mm_mfence // prevent before "releasing" a frame before we read everything.
frame.status = 0 // "release" a frame
_mm_fence
好的,现在编译器会在优化之前束手无策。
你怎么看?
编辑
[__mm_fence
在optimizations-out
之前并没有阻止这个问题。
@PeterCordes,请确保自己:__mm_fence
不会在优化之前阻止它(这只是x86内存屏障,不是编译器)。但是,atomic_thread_fence(any_order)
可以防止在重新排序之前(显然,它取决于any_order
),但是还可以防止在优化之前进行重新排序?
例如:
// x is an int pointer
*x = 5
*(x+4) = 6
std::atomic_thread_barrier(memory_order_release)
在优化从商店到x
之前预防?似乎必须-否则x
的每个存储都应为volatile
。
但是,我看到了很多无锁代码,没有making fields
和volatile
一样。
答案 0 :(得分:0)
_mm_mfence
也是 的编译器障碍。 (请参见When should I use _mm_sfence
_mm_lfence
and _mm_mfence
,以及BeeOnRope的答案)。
atomic_thread_fence
阻止较早的存储与以后的存储合并。但是mo_acquire
不必这样做。
写入非原子全局变量只能通过与写入相同非原子变量的 other 合并来优化,而不能通过完全优化它们来实现。因此,真正的问题是会发生什么重新排序,从而使两个非原子分配合并在一起。
必须在某处为原子变量赋值,以使任何其他线程可以与之同步的内容。一些编译器可能会给atomic_thread_fence
带来更强的行为。非原子变量,但是在C ++ 11中,没有其他线程可以合法地观察有关*x
和x[4]
在
#include <atomic>
std::atomic<int> shared_flag {0};
int x[8];
void writer() {
*x = 0;
x[4] = 0;
atomic_thread_fence(mo_release);
x[4] = 1;
atomic_thread_fence(mo_release);
shared_flag.store(1, mo_relaxed);
}
要存储到shared_flag的存储必须出现在存储到x[0]
和x[4]
的后面,但这只是一个实现细节,详细说明了存储到x[0]
和x[4]
的顺序,以及x[4]
是否有2家商店。
例如,on the Godbolt compiler explorer gcc7和更早的版本将商店合并到x[4]
,但gcc8不会,而clang或ICC都不会。旧的gcc行为不是违反ISO C ++标准,但我认为它们加强了gcc的thread_fence,因为它的强度不足以防止其他情况下的错误。
例如,
void writer_gcc_bug() {
*x = 0;
std::atomic_thread_fence(std::memory_order_release);
shared_flag.store(1, std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_release);
*x = 2; // gcc7 and earlier merge this, which arguably a bug
}
gcc仅按该顺序执行shared_flag = 1; *x = 2;
。您可能会争辩说,看到*x
之后,另一个线程无法安全地观察shared_flag == 1
,因为 this 线程会立即将其再次写入而没有同步。 (即,任何潜在观察者中的数据竞争UB都可以使这种重新排序合法化。)
但是gcc开发人员认为这还不够,(这可能违反了__atomic
标头用于实现API的内置<atomic>
函数的保证)。在其他情况下,甚至存在一个真正的错误,即使是符合标准的程序也可能会观察到违反标准的激进重新排序。
显然,此问题在2017-09年更改为gcc bug 80640。
我认为错误是x86
__atomic_thread_fence(x)
在x!=__ATOMIC_SEQ_CST
上扩展为空时,应该放置类似于__atomic_signal_fence
扩展的编译器障碍。
({__atomic_signal_fence
包含与asm("" ::: "memory" )
一样强的东西。)
是的,那肯定是一个错误。因此,并不是说gcc确实很聪明,并且允许重新排序,它主要是在thread_fence
上失败了,并且发生的任何正确性都是由于其他因素造成的,例如非内联函数边界! (而且它不会优化原子,只会优化非原子。)