繁体   English   中英

重新排序和memory_order_relaxed

[英]Reordering and memory_order_relaxed

Cppreference给出以下有关memory_order_relaxed 示例

标记为memory_order_relaxed原子操作不是同步操作,它们不对内存进行排序。 它们仅保证原子性和修改顺序的一致性。

然后说明,此示例代码在xy最初为零的情况下

// Thread 1:
r1 = y.load(memory_order_relaxed); // A
x.store(r1, memory_order_relaxed); // B

// Thread 2:
r2 = x.load(memory_order_relaxed); // C 
y.store(42, memory_order_relaxed); // D

允许产生r1 == r2 == 42因为:

  1. 尽管在线程1中A B 之前排在前面,而在线程2中C D 之前排在前面,
  2. 没有什么可以阻止D以y的修改顺序出现在A之前,而B是以x的修改顺序出现在C之前。

现在我的问题是:如果不能在线程1中对A和B进行重新排序,并且类似地在线程2中对C和D进行重新排序(因为它们中的每一个都在其线程内先后排序 ), 那么点1和点2是否不矛盾 换句话说, 在没有重新排序的情况下 (似乎要指出第1点),如何甚至可视化下面第2点中的情况?

T1 ...... T2

.............. D(y)

A(y)的

B(x)的

.............. C(x)

因为在这种情况下C将测序-之前线程2内d,如点1级的要求。

无需重新排序(如第1点所示)

点1并不意味着“不重新排序”。 这意味着在执行线程中对事件进行排序。 编译器将在B之前向A发出CPU指令,而在D之前向C发出CPU指令(尽管即使被as-if规则颠覆),但CPU也没有义务按照该顺序执行它们,即缓存/写入缓冲区/无效队列没有义务按该顺序传播,并且内存也没有统一的义务。

(尽管个别架构可以提供这些保证)

根据这篇文章的STR类推: C ++ 11引入了标准化的内存模型。 这是什么意思? 它将如何影响C ++编程? ,我创建了可视化效果图(据我了解),如下所示:

在此处输入图片说明

线程1首先看到y=42 ,然后执行r1=y之后执行x=r1 线程2首先看到x=r1已经是42,然后执行r2=x之后y=42

行代表各个线程的内存“视图”。 这些线/视图不能跨越特定线程。 但是,使用轻松的原子,一个线程的线/视图可以与其他线程的线/视图交叉。

编辑:

我想这与以下程序相同:

atomic<int> x{0}, y{0};

// thread 1:
x.store(1, memory_order_relaxed);
cout << x.load(memory_order_relaxed) << y.load(memory_order_relaxed);

// thread 2:
y.store(1, memory_order_relaxed);
cout << x.load(memory_order_relaxed) << y.load(memory_order_relaxed);

可以在输出上产生0110 (SC原子操作不会发生这样的输出)。

您对文本的解释是错误的。 让我们分解一下:

标记为memory_order_relaxed原子操作不是同步操作,它们不对内存进行排序

这意味着这些操作不能保证事件的顺序。 如原文中该语句之前所述,允许多线程处理器对单个线程内的操作进行重新排序。 这可能会影响写入,读取或两者。 此外,允许编译器在编译时执行相同的操作(主要是出于优化目的)。 为了了解这与示例之间的关系,假设我们根本不使用atomic类型,但是我们使用的是原子设计的原始类型(8位值...)。 让我们重写示例:

// Somewhere...
uint8_t y, x;

// Thread 1:
uint8_t r1 = y; // A
x = r1; // B

// Thread 2:
uint8_t r2 = x; // C 
y = 42; // D

考虑到编译器和CPU都允许在每个线程中对操作进行重新排序,很容易看出x == y == 42的可能性。


语句的下一部分是:

它们仅保证原子性和修改顺序的一致性。

这意味着唯一的保证是每个操作都是原子操作,也就是说,不可能“中途”观察到该操作。 这意味着如果xatomic<someComplexType> ,则一个线程不可能观察到x在状态之间具有值。


应该已经清楚了在哪里有用,但是让我们研究一个特定的示例(仅出于演示目的,这不是您想要的编码方式):

class SomeComplexType {
  public:
    int size;
    int *values;
}

// Thread 1:
SomeComplexType r = x.load(memory_order_relaxed);
if(r.size > 3)
  r.values[2] = 123;

// Thread 2:
SomeComplexType a, b;
a.size = 10; a.values = new int[10];
b.size = 0; b.values = NULL;
x.store(a, memory_order_relaxed);
x.store(b, memory_order_relaxed);

atomic类型对我们的作用是确保线程1中的r在两个状态之间不是对象,尤其是它的sizevalues属性是同步的。

仅查看C ++内存模型(不讨论编译器或硬件重新排序),导致r1 = r2 = 42的唯一执行是:

在此处输入图片说明

在这里,我将r1替换为a,将r2替换为b。 像往常一样,sb代表先于顺序,并且仅是线程间排序(指令在源代码中出现的顺序)。 rf是“ Read-From”边缘,表示一端的“读取/加载”读取另一端写入/存储的值。

对于循环来说,包含sb和rf边缘的循环(以绿色突出显示)对于结果是必不可少的:y被写在一个线程中,在另一个线程中被读取到a中,然后从那里被写入x,在前者中被读取。再次线程到b(在写入y之前先排序)。

为什么不能像这样构造图有两个原因:因果关系和因为RF读取了隐藏的副作用。 在这种情况下,后者是不可能的,因为我们只向每个变量写入一次,因此很明显一个写入不能被另一个写入隐藏(覆盖)。

为了回答因果关系问题,我们遵循以下规则:当循环涉及单个内存位置且sb边的方向在循环中的每个位置都在同一方向(rf边的方向)时,不允许循环(不可能)在这种情况下不相关); 或者,该循环涉及多个变量,所有边沿(sb和rf)都在同一方向上,并且至少一个变量在不释放/获取的不同线程之间具有一个或多个rf边沿。

在这种情况下,存在循环,涉及两个变量(x的一个rf边和y的一个rf边),所有边都在同一方向上,但是两个变量具有松弛/松弛的rf边(即x和y)。 因此,没有因果关系违规,并且此执行与C ++内存模型一致。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM