C++ std::memory_order_relaxed 混乱
C++ std::memory_order_relaxed confusion
我正在从 GCC Wiki 上阅读 this article 关于 C++ 内存屏障(及其令人敬畏的内容)的内容。
在我到达这一点之前,它非常简单:
相反的做法是std::memory_order_relaxed。该模型通过删除 happens-before 限制允许更少的同步。这些类型的原子操作也可以对其执行各种优化,例如删除死存储和共享。
所以在前面的例子中:
-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 中的排序不同步?
松散排序是相对于其他内存访问的操作排序,而不是相对于原子relax-modified的排序。在您的第一种情况下,您可以在 x
中看到 10 这一事实对于 y
的值没有任何意义。还有 vice-versa.
但是你的第二种情况不同,因为它影响的是同一个原子对象。
[intro.races]/10 tells us that, within a thread, if one operation is sequenced before another, then that operation "happens before" the other. And [intro.races]/14-17 outline the following behavior with regard to atomics:
The four preceding coherence requirements effectively disallow compiler reordering of atomic operations to a single object, even if both operations are relaxed loads.
这就是您所拥有的。所有修改都发生在同一个对象上,因此它们必须以 some 顺序发生。即使无法准确确定该顺序,该顺序也必须遵守代码的“先于发生”关系。
线程 1 的两个操作按“先于发生”关系排序。并且线程 2 的操作本身是按“发生在之前”的关系排序的。
因为它们都作用于同一个原子对象,如果 y
得到值 2,那么它一定是“发生在”x
被设置为 2 之后。所以顺序x
的访问权限必须是“x = 1,x = 2,读取 x”。由于最后一次读取 x
发生在第一次读取 x
之后,因此它获得的值不能是 1.
我正在从 GCC Wiki 上阅读 this article 关于 C++ 内存屏障(及其令人敬畏的内容)的内容。
在我到达这一点之前,它非常简单:
相反的做法是std::memory_order_relaxed。该模型通过删除 happens-before 限制允许更少的同步。这些类型的原子操作也可以对其执行各种优化,例如删除死存储和共享。 所以在前面的例子中:
-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 中的排序不同步?
松散排序是相对于其他内存访问的操作排序,而不是相对于原子relax-modified的排序。在您的第一种情况下,您可以在 x
中看到 10 这一事实对于 y
的值没有任何意义。还有 vice-versa.
但是你的第二种情况不同,因为它影响的是同一个原子对象。
[intro.races]/10 tells us that, within a thread, if one operation is sequenced before another, then that operation "happens before" the other. And [intro.races]/14-17 outline the following behavior with regard to atomics:
The four preceding coherence requirements effectively disallow compiler reordering of atomic operations to a single object, even if both operations are relaxed loads.
这就是您所拥有的。所有修改都发生在同一个对象上,因此它们必须以 some 顺序发生。即使无法准确确定该顺序,该顺序也必须遵守代码的“先于发生”关系。
线程 1 的两个操作按“先于发生”关系排序。并且线程 2 的操作本身是按“发生在之前”的关系排序的。
因为它们都作用于同一个原子对象,如果 y
得到值 2,那么它一定是“发生在”x
被设置为 2 之后。所以顺序x
的访问权限必须是“x = 1,x = 2,读取 x”。由于最后一次读取 x
发生在第一次读取 x
之后,因此它获得的值不能是 1.