C ++ 11原子。可见性和thread.join()/停止线程的正确方法

时间:2019-02-15 15:36:55

标签: c++ multithreading c++11 memory-visibility

C ++ 11保证在哪个STORE_ORDERLOAD_ORDER中确保这段代码在有限的时间内运行?

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

我看到了两个问题:

存储顺序

在我看来,使用releaseaquire,允许编译器和cpu在join之前对我的store进行重新排序(假设它的行为像加载一样)。 ,这当然可以解决这个问题。

即使使用memory_order_seq_cst,也不确定是否禁止这种重新排序,因为我不知道join()是否实际上进行了任何装载或存储。

可见度

如果我正确理解了this question about memory_order_relaxed,则不能保证带有memory_order_relaxed的存储在有限的时间内对其他线程可见。其他订单是否有这样的保证?

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


要进一步迈出这一步-如果我有局限性,也对速度有所承诺会很好。我认为C ++标准没有做出任何这样的承诺。但是,是否有任何编译器或特定于x86的方法来保证该存储将很快对另一个线程可见?


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

2 个答案:

答案 0 :(得分:1)

经过更多搜索,我发现了一个与我的能见度部分相同的问题,得到a clear answer:确实没有没有这样的保证 –仅存在以下要求: “实施应使原子存储在合理的时间内对原子负载可见” 。该标准并没有定义应指的​​是什么,但是我将假设其正常含义,因此这将是非约束性的。还不清楚“合理”的含义,但我认为它显然排除了“无限”。

这并不能完全回答有关内存排序的问题。但是,如果在join()之后下达存储命令(这可能会永远阻塞),则该存储将永远不会对其他线程可见-这将不是“合理的时间”。

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

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

答案 1 :(得分:0)

  

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

使用不会损坏的编译器,以确保将thread.join()正确地视为可以读取或写入任何内存的“黑匣子”函数。

因此,编译器将必须确保内存“在线程完成阻塞之前”与C ++抽象机 同步。即,在编译时对存储进行重新排序可能会违反as-if规则,因此除非编译器可以证明不会这样做,否则编译器一定不能这样做(例如,对地址不能逃逸该函数的局部变量),此处不是这种情况,因此存储必须在call join之前甚至在mo_relaxed的x86 asm中也会发生。

(或者在一个假设的实现中,可以完全内联的join至少会具有像GNU C asm("":::"memory")atomic_thread_fence()这样的编译时内存屏障。直到acq_rel都不需要在x86上使用asm指令,只需阻止编译时重新排序即可。)

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

请参见Is mov + mfence safe on NUMA?When to use volatile with multi threading?进一步说明 asm如何无法在实际CPU(包括非x86)上无限期地看到过时的值。因此,编译时的程序顺序时间足够了。

同样的道理也适用于其他所有可以使用std::thread进行C ++实现的真实CPU。 (有些 一些异构的片上系统CPU在微控制器和DSP之间具有独立的一致性域,但是高质量的C ++实现std::thread不会在非一致性内核上启动线程。< / p>

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

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