Multithreading “a”的反面是什么;“完整内存屏障”;?

Multithreading “a”的反面是什么;“完整内存屏障”;?,multithreading,assembly,memory-barriers,Multithreading,Assembly,Memory Barriers,我有时会在关于内存排序的教程中看到“完整内存屏障”一词,我认为它的意思如下: 如果我们有以下说明: instruction 1 full_memory_barrier instruction 2 instruction 1 memory_barrier_below_to_above instruction 2 然后,指令1不允许重新排序到低于满内存屏障,指令2不允许重新排序到高于满内存屏障 但是什么是完全内存屏障的反面,我的意思是,是否有类似于“半内存屏障”的东西,只阻止CPU在一个方向上

我有时会在关于内存排序的教程中看到“完整内存屏障”一词,我认为它的意思如下:

如果我们有以下说明:

instruction 1
full_memory_barrier
instruction 2
instruction 1
memory_barrier_below_to_above
instruction 2
然后,
指令1
不允许重新排序到低于
满内存屏障
指令2
不允许重新排序到高于
满内存屏障


但是什么是完全内存屏障的反面,我的意思是,是否有类似于“半内存屏障”的东西,只阻止CPU在一个方向上重新排列指令

如果存在这样的记忆障碍,我看不出它的意义,我的意思是如果我们有以下说明:

instruction 1
full_memory_barrier
instruction 2
instruction 1
memory_barrier_below_to_above
instruction 2
假设
下面的
内存\u屏障\u到上面的
是一个内存屏障,它阻止
指令2
被重新排序到上面的
下面的内存\u屏障\u到上面的
,因此不允许出现以下情况:

instruction 2
instruction 1
memory_barrier_below_to_above
但以下情况是允许的(这使得这种类型的内存屏障毫无意义):

解释不同类型的障碍,如Load或Store。StoreStore屏障只能防止商店跨屏障重新排序,但加载仍可能无序执行

在真正的CPU上,任何包含StoreLoad的屏障也会阻塞其他所有内容,因此称为“完全屏障”。StoreLoad是最昂贵的一种,因为它意味着在以后的加载可以从L1d缓存读取之前,先耗尽存储缓冲区

屏障示例:

           strong               weak
x86        mfence               none needed unless you're using NT stores
ARM        dmb sy               isb,  dmb st, dmb ish, etc.
POWER      hwsync               lwsync, isync, ...
ARM有“内部”和“外部可共享域”。我真的不知道这意味着什么,也没有必要处理它,但记录了可用的不同形式的数据存储屏障
dmb st
只等待较早的存储完成,所以我认为这只是一个StoreStore障碍,因此对于C++11发行版存储来说太弱了,它还需要根据LoadStore的重新排序来订购较早的加载。另请参见:请注意,可以在每个仓库周围设置完整的屏障,或在装载之前以及仓库之前设置屏障,以实现seq cst。不过,让货物便宜通常是最好的

ARM ISB刷新指令缓存。(ARM没有连贯的i-cache,所以在将代码写入内存后,您需要一个ISB,然后才能可靠地跳转到内存并将这些字节作为指令执行。)

POWER有很多可用的屏障,包括Jeff Preshing在上面链接的文章中提到的轻型(非全屏障)和重型同步(全屏障)


单向屏障是您从发布存储或获取负载中获得的。临界段末端的释放存储(例如解锁自旋锁)必须确保临界段内的加载/存储不会在以后出现,但不必延迟以后的加载,直到
lock=0
变得全局可见

Jeff Preshing也有一篇关于这方面的文章:


“完整”与“部分”屏障术语通常不用于发布存储或获取加载的单向重新排序限制。与特定对象上的发布存储不同,实际的发布隔离(在C++11中,
std::atomic_thread_fence(std::memory_order_release)
)会阻止存储在两个方向上的重新排序

这种微妙的区别在过去曾引起过混乱(甚至在专家中也是如此!)。杰夫·普雷辛(Jeff Preshing)还有一篇优秀的文章对此进行了解释:

你是对的,一个没有绑在商店或货物上的单向屏障不会很有用;这就是为什么这样的东西不存在的原因P它可以在一个方向上对无限距离进行重新排序,并将所有操作留给彼此重新排序


原子线程围栏(内存\u顺序\u释放)
具体做什么?

C11()仅根据创建与获取加载或获取围栏的同步关系来定义它,当释放围栏在加载访问的同一对象的原子存储(放松或其他)之前使用时。(C++11的定义基本相同,但在注释中与@EOF的讨论提出了C11版本。)

这个净效应的定义,而不是实现它的机制,并没有直接告诉我们它允许或不允许什么。例如,第3小节说

3) 释放栅栏A与执行获取的原子操作B同步 如果存在原子操作X,则原子对象M上的操作 在X之前排序,X修改M,B读取X写入的值或写入的值 假想的释放序列中的任何副作用,如果是释放,X都会领先 操作

因此,在编写线程中,它讨论的代码如下:

stuff           // including any non-atomic loads/stores

atomic_thread_fence(mo_release)  // A
M=X                              // X
  // threads that see load(M, acquire) == X also see stuff
与同步意味着线程可以看到
M=X
(直接或间接通过发布序列)中的值,也可以看到所有
内容
,并读取非原子变量,而不需要数据

这让我们谈谈什么是/不允许的:

这是原子存储的双向屏障。它们在两个方向上都不能穿过它,因此该障碍在该线程内存顺序中的位置受前后原子存储的限制。对于某些
M
,任何较早的存储都可以是
stuff
的一部分,任何较晚的存储都可以是采集加载(或加载+采集围栏)与之同步的
M

这是原子负载的单向屏障:早期的需要停留在屏障之前,但后期的可以移动到屏障之上
M=X
只能是门店(或RMW的门店部分)

这是非原子加载/存储的单向屏障:非原子存储可以是
东西的一部分,但不能是
X
,因为它们不是原子的。允许此线程中的后续加载/存储在
M=X之前显示给其他线程是可以的<