宽松的原子计数器安全吗?

Ada*_*dam 8 c++ concurrency multithreading synchronization atomic

根据 C++11 内存模型,以下代码是否保证返回计数器的预期值 (40,000,000)?(不限于 x86)。

\n\n
#include <atomic>\n#include <thread>\nusing namespace std;\n\nvoid ThreadProc(atomic<int>& counter)\n{\n    for (int i = 0; i < 10000000; i++)\n        counter.fetch_add(1, memory_order_relaxed);\n}\n\nint main()\n{\n    #define COUNT 4\n    atomic<int> counter = { 0 };\n    thread threads[COUNT] = {};\n\n    for (size_t i = 0; i < COUNT; i++)\n        threads[i] = thread(ThreadProc, ref(counter));\n\n    for (size_t i = 0; i < COUNT; i++)\n        threads[i].join();\n\n    printf("Counter: %i", counter.load(memory_order_relaxed));\n    return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

特别是,宽松的原子是否会进行协调,使得两个线程不会同时读取当前值、独立地递增它,并且都写入其递增的值,从而有效地丢失其中一个写入?

\n\n

规范中的一些行似乎表明在上面的示例中计数器必须始终为 40,000,000。

\n\n
\n

[注意:指定 memory_order_relaxed 的操作在内存排序方面是宽松的。实现仍然必须保证对特定原子对象的任何给定原子访问相对于对该对象的所有其他原子访问是不可分割的。\xe2\x80\x94 尾注

\n
\n\n

\n\n
\n

原子读-修改-写操作应始终读取与读-修改-写操作关联的写入的最后一个值(按修改顺序)。

\n
\n\n

\n\n
\n

对特定原子对象 M 的所有修改都以某种特定的总顺序发生,称为 M 的修改顺序。如果 A 和 B\n 是原子对象 M 的修改,并且 A 发生在(如下定义)B 之前,则 A 应按照 M 的修改顺序先于 B,其定义如下。

\n
\n\n

本演讲还支持上述代码不存在种族问题的观点。\n https://www.youtube.com/watch?v=KeLBd2EJLOU&feature=youtu.be&t=1h9m30s

\n\n

在我看来,原子操作存在不可分割的顺序,但我们不能保证顺序是什么。因此,所有增量都必须“一个在另一个之前”发生,而不需要我上面描述的竞赛。

\n\n

但接下来的一些事情可能指向另一个方向:

\n\n
\n

实现应该使原子存储在合理的时间内对原子加载可见。

\n
\n\n

一位同事告诉我,萨特的演讲中存在已知的错误。尽管我还没有找到任何来源。

\n\n

C++ 社区的多个成员比我更聪明,他们暗示可以缓冲宽松的原子添加,以便后续的宽松原子添加可以读取陈旧值并对其进行操作。

\n

LWi*_*sey 7

您问题中的代码是无种族的;所有增量均已排序,并且保证结果为 40000000。
您问题中的参考文献包含标准中的所有相关引用。

其中所说的原子存储应该在合理的时间内可见的部分仅适用于单个存储。
在您的情况下,计数器通过原子读取-修改-写入操作递增,并且保证这些操作按修改顺序进行最新操作。

C++ 社区的多个成员 (...) 暗示可以缓冲宽松的原子添加,以便后续的宽松原子添加可以读取陈旧值并对其进行操作。

只要修改基于原子读-修改-写操作,这是不可能的。
如果标准不能保证可靠的结果,原子增量将毫无用处

  • @Adam这些排序参数定义了原子RMW本身如何对其他内存操作进行排序,但它们不会改变计数器增量的结果 (2认同)