C++11:memory_order_relaxed 和 memory_order_consume 的区别

标签 c++ c++11 memory-model stdatomic

我现在正在学习C++11 memory order model并想了解 memory_order_relaxedmemory_order_consume 之间的区别。

具体来说,我正在寻找一个无法将 memory_order_consume 替换为 memory_order_relaxed 的简单示例。

有一个优秀的post它详细阐述了一个简单但非常具有说明性的示例,其中可以应用 memory_order_consume。以下是文字复制粘贴。

例子:

atomic<int*> Guard(nullptr);
int Payload = 0;

制作人:

Payload = 42;
Guard.store(&Payload, memory_order_release);

消费者:

g = Guard.load(memory_order_consume);
if (g != nullptr)
    p = *g;

我的问题由两部分组成:

  1. 能否将上面示例中的 memory_order_consume 替换为 memory_order_relaxed
  2. 能否举出一个类似的示例,其中 memory_order_consume 不能替换为 memory_order_relaxed

最佳答案

问题一

没有。
memory_order_relaxed 完全不强加任何内存顺序:

Relaxed operation: there are no synchronization or ordering constraints, only atomicity is required of this operation.

虽然 memory_order_consume 对数据相关读取(在当前线程上)强加内存排序

A load operation with this memory order performs a consume operation on the affected memory location: no reads in the current thread dependent on the value currently loaded can be reordered before this load.

编辑

一般memory_order_seq_cstmemory_order_acq_relmemory_ordering_relaxed
这就像有一部电梯 A 可以举起 800 公斤的电梯 C 可以举起 100 公斤。
现在,如果你有能力神奇地将电梯 A 变成电梯 C,如果前者挤满了 10 个平均体重的人,会发生什么? 那会很糟糕。

要准确了解代码可能出现的问题,请考虑您问题中的示例:

Thread A                                   Thread B
Payload = 42;                              g = Guard.load(memory_order_consume);
Guard.store(1, memory_order_release);      if (g != 0)
                                               p = Payload;

此代码段旨在循环,两个线程之间没有同步,只有排序。

使用memory_order_relaxed,并假设自然词加载/存储是原子的,代码将等同于

Thread A                                   Thread B
Payload = 42;                              g = Guard
Guard = 1                                  if (g != 0)
                                               p = Payload;

从 CPU 的角度来看,线程 A 有两个存储到两个单独的地址,因此如果 Guard 与另一个处理器的 CPU“更近”(意味着存储将完成得更快),它似乎线程 A 正在执行

Thread A
Guard = 1
Payload = 42

而且这个执行顺序是可以的

Thread A   Guard = 1
Thread B   g = Guard
Thread B   if (g != nullptr) p = Payload
Thread A   Payload = 42

这很糟糕,因为线程 B 读取了一个未更新的 Payload 值

然而,在线程 B 中,同步似乎是无用的,因为 CPU 不会像这样重新排序

Thread B
if (g != 0) p = Payload;
g = Guard

但它确实会。

从它的角度来看,有两个不相关的负载,一个确实在依赖数据路径上,但 CPU 仍然可以推测性地完成负载:

Thread B
hidden_tmp = Payload;
g = Guard
if (g != 0) p = hidden_tmp

可能会生成序列

Thread B   hidden_tmp = Payload;
Thread A   Payload = 42;
Thread A   Guard = 1;
Thread B   g = Guard
Thread B   if (g != 0) p = hidden_tmp

糟糕。

问题2

总的来说,这是永远做不到的。
当您要在加载的值和需要对其访问进行排序的值之间生成地址依赖性时,您可以将 memory_order_acquire 替换为 memory_order_consume


理解memory_order_relaxed可以引用ARM架构。
ARM 体系结构仅要求弱内存排序,这意味着通常程序的加载和存储可以任何顺序执行。

str r0, [r2]
str r0, [r3]

在存储到 [r2]1 之前,可以从外部观察到存储到 [r3] 的代码段。

然而,CPU 并没有达到 Alpha CPU 的水平,并强加了 two kinds of dependencies : address dependency,当内存中的值加载用于计算另一个加载/存储的地址时,以及 control dependency,当内存中的值加载用于计算时另一个加载/存储的控制标志。

在存在这种依赖性的情况下,两个内存操作的顺序保证为 visible in program order :

If there is an address dependency then the two memory accesses are observed in program order.

因此,虽然 memory_order_acquire 会生成内存屏障,但使用 memory_order_consume 时,您是在告诉编译器您使用加载值的方式会生成地址依赖性因此,如果与架构相关,它可以利用这一事实并省略内存屏障。


1 如果r2 是一个同步对象的地址,那就不好了。

关于C++11:memory_order_relaxed 和 memory_order_consume 的区别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38280633/

相关文章:

C++ OpenGL 闪烁问题

c++ - constexpr 移动构造函数是否有意义?

c++ - 什么时候不应该使用[[carries_dependency]]?

c++ - 将GPU版本的FFMPEG编译为OpenCV

c++ - 如何隐式调用 C 风格的清洁函数?

c++ - 当 lib 文件夹中同时存在静态和共享 C++ 库时,仅链接静态库

c++ - 创建新线程时在 lambda 中使用 unique_ptr 的线程安全性

c++ - 允许 tr1::function 吞下返回值的解决方法

c++ - 使用空类类型对象的内存

Java 内存模型 : Is it safe to create a cyclical reference graph of final instance fields, 全部在同一个线程中分配?