我正在从GCC Wiki阅读有关C++内存障碍(及其令人敬畏)的this article。
直到我明白这一点之前,这还是很简单的:
相反的方法是std::memory_order_relaxed。通过消除事前发生的限制,该模型允许少得多的同步。这些类型的原子操作也可以对其执行各种优化,例如死存储的删除和通用。
因此,在前面的示例中:
-Thread 1-
y.store (20, memory_order_relaxed)
x.store (10, memory_order_relaxed)
-Thread 2-
if (x.load (memory_order_relaxed) == 10)
{
assert (y.load(memory_order_relaxed) == 20) /* assert A */
y.store (10, memory_order_relaxed)
}
-Thread 3-
if (y.load (memory_order_relaxed) == 10)
assert (x.load(memory_order_relaxed) == 10) /* assert B */
由于不需要跨系统同步线程,因此在此示例中的任何一个断言实际上都可能失败。好的,这也很简单,让我们继续。
-Thread 1-
x.store (1, memory_order_relaxed)
x.store (2, memory_order_relaxed)
-Thread 2-
y = x.load (memory_order_relaxed)
z = x.load (memory_order_relaxed)
assert (y <= z)
断言不能失败。一旦线程2看到了2的存储,它将不再看到值1。这可以防止在可能混叠的其他引用的松弛负载之间合并一个变量的松弛负载。这就是让我感到困惑的原因,为什么y无法加载值2,而z无法加载值1(并导致断言失败),因为排序未在线程1中同步?
最佳答案
放宽的排序是相对于其他内存访问的操作排序,而不是相对于被放宽修改的原子的排序。在第一种情况下,您可以在x
中看到10的事实与y
的值无关。反之亦然。
但是您的第二种情况是不同的,因为它会影响同一个原子对象。
[intro.races]/10告诉我们,在一个线程中,如果一个操作在另一个操作之前被排序,则该操作在另一个操作之前“发生”。和[intro.races]/14-17 outline the following behavior with regard to atomics:
这就是您在这里的东西。所有修改都发生在同一对象上,因此必须按一定顺序进行。即使不能准确确定该顺序,该顺序也必须遵守代码的“先发生”关系。
线程1的两个操作由“先发生”关系排序。线程2的操作本身按“先发生”的关系进行排序。
由于它们都作用于同一原子对象,因此,如果y
的值为2,则必须在“x
”设置为2之后“发生”。因此,x
的访问顺序必须为“x = 1,x = 2,读取x“。并且由于最后一次读取x
是在第一次读取x
之后发生的,因此它获得的值不能为1。