4

对于哪个(如果有的话?)STORE_ORDER& LOAD_ORDERC++11 是否保证此代码在有限时间内运行?

std::atomic<bool> a{false};
std::thread t{[&]{
  while(!a.load(LOAD_ORDER));
}};
a.store(true, STORE_ORDER);
t.join();

我看到两个问题:

内存顺序

在我看来,使用release& aquire,编译器和 cpu 可以在 之前重新排序 my join(假设它的行为像负载)store,这当然会破坏这一点。

即使使用memory_order_seq_cst,我也不确定是否禁止此类重新排序,因为我不知道是否join()真的进行了任何加载或存储。

能见度

如果我正确理解了这个问题memory_order_relaxed,则不能保证memory_order_relaxed在有限的时间内其他线程可以看到带有的商店。其他订单是否有这样的保证?

我知道这std::atomic是关于原子性和内存排序,而不是关于可见性。但我不知道 c++11 中的任何其他工具可以帮助我。我是否需要使用特定于平台的工具来获得正确性保证,如果是,是哪一个?


更进一步——如果我有有限性,那么对速度也有一些承诺会很好。我不认为 C++ 标准做出任何这样的承诺。但是是否有任何编译器或特定于 x86 的方法来保证存储将很快对其他线程可见?


总结:我正在寻找一种方法来快速停止实际上保证具有此属性的工作线程。理想情况下,这将与平台无关。但如果我们不能拥有它,它至少存在于 x86 中吗?

4

2 回答 2

2

经过一番搜索,我发现了一个与我的可见性部分相同的问题,得到了明确的答案:确实没有这样的保证——只有一个请求“实现应该使原子存储对原子负载内的原子负载可见”合理的时间”。该标准没有定义应该是什么意思,但我会假设正常的意思,所以这将是不具约束力的。也不太清楚“合理”是什么意思,但我认为它显然不包括“无限”。

这并不能完全回答有关内存排序的问题。但是,如果 store 是在 之后订购的join(),这可能会永远阻塞,那么 store 将永远不会对其他线程可见——这不是一个“合理的时间量”。

因此,虽然标准不要求问题中的代码有效,但它至少表明它应该是有效的。作为奖励,它实际上说它不应该只是有限的时间,而且应该有点快(或者说是合理的)。

这留下了关于特定于平台的解决方案的部分问题:是否有特定于 x86 的方法来编写请求的算法,因此它实际上可以保证是正确的?

于 2019-02-16T01:21:51.300 回答
1

是否有特定于 x86 的方法来编写请求的算法,因此它实际上可以保证是正确的?

使用未损坏的编译器以确保将thread.join()其正确视为可能读取或写入任何内存的“黑盒”函数。

因此,编译器必须确保内存“在阻塞线程完成之前与 C++ 抽象机同步。即在编译时重新排序存储可能违反 as-if 规则,因此编译器不得这样做,除非他们能证明它不会(例如,对于地址不转义函数的局部变量)。这里不是这种情况,因此存储必须在 x86 asmcall join甚至 for之前发生mo_relaxed

(或者在一个假设的实现中,join可以完全内联的 a 至少有一个编译时内存屏障,如 GNU Casm("":::"memory")或者可能atomic_thread_fence()具有一定的强度。在 acq_rel 之前的任何东西都不需要 x86 上的 asm 指令,只是阻止编译时重新排序。)

x86 CPU 内核通过一致的高速缓存共享一致的内存视图。(MESI 协议或等效协议)。一旦存储从存储缓冲区提交到 L1d 缓存,任何其他内核就不可能读取“陈旧”值。现代 x86、IIRC 上的内核间延迟通常为 40 到 100 纳秒(当两个线程已经在不同的物理内核上运行时)。

请参阅mov + mfence 在 NUMA 上是否安全?以及何时将 volatile 与多线程一起使用?解释更多关于asm 如何在真实 CPU(包括非 x86)上无限期地看到陈旧值。 所以编译时的程序顺序在这里就足够了。

相同的推理适用于正常的 C++ 实现可以使用std::thread. (有一些异构片上系统 CPU 在微控制器和 DSP 之间具有独立的相干域,但高质量的 C++ 实现std::thread不会跨非相干内核启动线程。

或者,如果一个实现确实在没有一致缓存的内核上运行,则它std::atomic必须在宽松原子存储之后刷新缓存行。也许在放松的原子负载之前。或者在发布存储之前同步/写回整个缓存中的任何脏数据。因此,在非连贯/显式连贯系统之上实现 C++ 的内存模型将非常低效。它必须足够连贯,原子 RMW 才能按描述工作(释放序列等,并且任何时候都只有一个原子计数器的“实时”副本)。

这就是为什么您不会构建这样的 C++ 实现的原因。您可能允许在属于不同相干域(ARM 非“内部共享”)的其他内核上启动东西,但不能通过 std::thread,因为如果您不想让用户知道其含义共享变量在它们之间正常工作。

于 2019-10-30T22:58:07.687 回答