内存屏障不会让其他线程更快地看到您的存储。 (除了阻止稍后的加载可能会稍微减少提交缓冲存储的争用。)
存储缓冲区总是尝试尽快将已退休(已知的非推测性)存储提交到 L1d 缓存。缓存是连贯的1,因此由于 MESI/MESIF/MOESI,它们使它们全局可见。存储缓冲区没有设计为适当的缓存或写入组合缓冲区(尽管它可以将背靠背存储组合到同一缓存行),因此它需要清空自身以为新存储腾出空间。与缓存不同,它希望自己保持为空,而不是满。
注1:不仅仅是x86;我们可以在其内核上运行单个 Linux 实例的任何 ISA 的所有多核系统都必须是缓存一致的;Linux 依靠volatile
其手动原子来使数据可见。同样,C++std::atomic
加载/存储操作mo_relaxed
只是在所有普通 CPU 上进行简单的 asm 加载和存储,依靠硬件来实现内核之间的可见性,而不是手动刷新。
何时在多线程中使用 volatile?解释说。有一些集群,或混合微控制器 + DSP ARM 板具有非相干共享内存,但我们不会跨不同的相干域运行同一进程的线程。相反,您在每个集群节点上运行一个单独的操作系统实例。我不知道任何 C++ 实现在哪里atomic<T>
加载/存储包括手动刷新指令。(如果有的话请告诉我。)
栅栏/障碍通过使当前线程等待来工作
...直到通过正常机制发生所需的可见性。
全屏障(mfence
或lock
ed 操作)的简单实现是停止管道直到存储缓冲区耗尽,但高性能实现可以做得更好,并允许与内存顺序限制分开的乱序执行。
(不幸的是,Skylakemfence
确实完全阻止了乱序执行,以修复涉及从 WC 内存加载 NT 的模糊 SKL079 勘误表。但是lock add
或xchg
其他仅阻止稍后加载读取 L1d 或存储缓冲区,直到屏障到达存储末尾缓冲区。mfence
在早期的 CPU 上大概也没有这个问题。)
一般来说,在非 x86 架构上(对于较弱的内存屏障有显式的 asm 指令,比如只有 StoreStore 栅栏而不关心负载),原则是相同的:阻塞它需要阻塞的任何操作,直到该内核完成任何操作的早期操作类型。
有关的:
最终,我试图为自己回答的问题是线程 2 是否有可能在几秒钟内看不到线程 1 的写入
不,最坏情况的延迟可能类似于存储缓冲区长度(Skylake 上的 56 个条目,而 BDW 中的 42 个)乘以缓存未命中延迟,因为 x86 的强内存模型(没有 StoreStore 重新排序)要求存储按顺序提交. 但是多个缓存行的 RFO 可以同时运行,因此最大延迟可能是其 1/5(保守估计:有 10 个行填充缓冲区)。在飞行中(或来自其他核心)也可能存在来自负载的争用,但我们只需要一个数量级的粗略数。
假设 RFO 延迟(DRAM 或来自另一个内核)在 3GHz CPU 上是 300 个时钟周期(基本上是由组成的)。因此,商店成为全球可见的最坏情况延迟可能类似于300 * 56 / 5
= 3360 个核心时钟周期。所以在一个数量级内,最坏的情况是我们假设的 3GHz CPU 上大约 1 微秒。(CPU 频率抵消了,因此以纳秒为单位的 RFO 延迟估计会更有用)。
那时您的所有商店都需要等待很长时间才能获得 RFO,因为它们都位于未缓存或由其他内核拥有的位置。并且它们都不是背靠背到同一缓存行,因此没有一个可以合并到存储缓冲区中。所以通常你会期望它明显更快。
我认为没有任何合理的机制可以让它花费一百微秒,更不用说一整秒了。
如果您所有的存储都缓存其他核心都在争用同一行的行,那么您的 RFO 可能需要比正常时间更长的时间,因此可能需要数十微秒,甚至可能是一百微秒。但这种绝对最坏的情况不会偶然发生。