C ++,std :: atomic,什么是std :: memory_order以及如何使用它们?


73

任何人都可以解释什么是std::memory_order简单的英语,以及如何使用它们std::atomic<>吗?

我在这里找到了参考资料和一些示例,但根本不了解。 http://en.cppreference.com/w/cpp/atomic/memory_order


2
+1很好的问题。让我们希望得到一些好的答案!
干杯和健康。-Alf 2012年

1
过去,我发现这个(相当密集的)答案对内存排序很有帮助。
Luc Danton 2012年

看来这个话题可以深入。正如zvrba所建议的那样,作为像我这样的初学者,使用默认值“顺序一致的排序”可能是个好主意。
2607年

Answers:


25

谁能用简单的英语解释什么是std :: memory_order,

对于各种内存顺序,我发现的最好的“普通英语”解释是Bartoz Milewski的有关宽松原子的文章:http ://bartoszmilewski.com/2008/12/01/c-atomics-and-memory-ordering/

以及后续帖子:http : //bartoszmilewski.com/2008/12/23/the-inscrutable-c-memory-model/

但是请注意,尽管这些文章是不错的介绍,但它们早于C ++ 11标准,并且不会告诉您安全使用它们所需的一切。

以及如何在std :: atomic <>中使用它们?

在这里给您最好的建议是:不要。松弛原子(可能)是C ++ 11中最棘手和最危险的事情。坚持std::atomic<T>使用默认的内存顺序(顺序一致性),直到您真正确定要解决的性能问题可以通过使用宽松的内存顺序来解决。

在上面链接的第二篇文章中,Bartoz Milewski得出以下结论:

当我尝试推理C ++弱原子时,我不知道自己正在进入什么领域。它们背后的理论是如此复杂,以至于无法使用。花了三个人(安东尼,汉斯和我)和对标准的修改来完成相对简单算法的证明。想象一下,对于基于弱原子的无锁队列也是如此!


感谢您的建议。作为一个初学者,我发现这很有帮助。
2607年

1
请参阅Jeff Preshing的文章,如有关获取/发布的文章,有关出色的介绍性材料,它确实描述了C ++ 11以及根据允许/不允许的内存重新排序的种类来描述事物。(例如,允许对StoreLoad进行重新排序)
Peter Cordes,

2
relaxed更容易理解:仅在需要原子性而不需要同步时使用它。例如,对于单个原子计数器,多个线程将增加,但这对于其他任何数据是否有效没有任何意义。
彼得·科德斯

43

这些std::memory_order值使您可以对原子操作提供的内存顺序指定细粒度的约束。如果要从多个线程修改和访问原子变量,则将std::memory_order值传递给您的操作将使您可以放宽对这些原子变量的操作对其他线程可见的顺序以及对同步的约束,以限制编译器和处理器的工作。这些操作对应用程序中非原子数据的影响。

的默认顺序std::memory_order_seq_cst受最大约束,并提供您可能期望的“直观”属性:如果线程A存储了一些数据,然后使用设置了一个原子标志std::memory_order_seq_cst,则如果线程B看到该标志已设置,那么它可以看到写入的数据其他内存排序值不一定提供此保证,因此必须非常谨慎地使用。

基本前提是:不要使用std::memory_order_seq_cst(默认值)以外的任何东西,除非(a)您真的了解自己在做什么,并且可以证明在所有情况下放松使用都是安全的,并且(b)您的探查器表明您打算使用宽松排序的数据结构和操作是一个瓶颈。

我的书《C ++并发性行动》一整章(45页)专门介绍C ++内存模型,原子操作和std::memory_order约束的细节,另一章(44页)专门介绍了使用原子操作在无锁数据结构中进行同步。 ,以及放宽订购限制的后果。

我在有关Dekker算法Peterson算法互斥的博客文章中展示了一些问题。


2
放松对于真正需要的是非常安全的-读取没有依存/对应值的孤立值,因此没有排序问题的风险。例如,abool shouldHalt要求线程停止。以我的经验,这种隔离在普通软件开发中并不罕见。我认为,对于其他情况(多个变量之间的依赖关系),锁首先总是总是更好。我看到人们对此有困难的唯一方法是使用奇特的原子链接数据结构,除非有必要,否则这是要提防的真实事物。
VoidStar

在《 C ++并发行动》(第二版)中,std::memory_order在第5.2.1节中介绍后,您使用了许多不同的排序方式(甚至在解释它们在第5.3.3节中实际执行的操作之前),而不是遵循std::memory_order_seq_cst此答案中的建议。这是否表示此答案(或书)不正确,还是我误解了?

本书第5.2节中的示例指定了内存顺序,以说明您将如何执行内存以及您的选择。对于真实的代码,std::memory_order_seq_cst除非您真的知道自己在做什么,否则就应该坚持下去这是一个行之有效的性能瓶颈。
安东尼·威廉姆斯

18

不会。“普通英语”解释需要32页,可以在这里找到。

如果您不想阅读这些内容,则可以忽略内存排序,因为链接到的页面上说默认是顺序一致的排序,即“总是做明智的事情”设置。

要使用任何其他设置,您确实必须阅读并理解以上文章及其中的示例。


5

简而言之,您的编译器和CPU可能会按与编写它们的方式不同的顺序执行指令。对于单线程,这不是问题,因为它将看起来正确。对于多个处理器上的多个线程,这成为一个问题。C ++中的内存排序限制了编译器/ CPU的功能,并解决了此类问题。

例如,如果您看一下我关于双重检查锁定的文章,您会发现排序如何与该模式混淆-它提到显示原子存储排序可用于修复它。

关于重新排序本身,您还可以考虑CPU重新排序-再次,编译器也可能会进行重新排序。

请注意,有关此主题的任何文档(包括我的文档)都提供了理论上的讲解。最常见的CPU(例如x86)具有非常强的排序保证,因此根本不需要很多显式排序。因此,即使您没有使用正确的C ++ 11原子,您的代码也可能仍然有效。

正如zvrba所述,该主题实际上非常详细。关于内存屏障的linux内核文档也包含很多详细信息。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.