如何理解 std::memory_order 中的 RELAXED ORDERING (C++)
How to understand RELAXED ORDERING in std::memory_order (C++)
我阅读了很多帖子并观看了几个 Youtube 视频 C++ 原子和内存模型(ConCpp 17、14)。
当我阅读 Concurrency In Action 一书时,第 5.3.3 节 RELAXED ORDERING,我仍然无法理解提供的示例作者在他的假设下。
作者的假设
It’s not just that the compiler can reorder the instructions. Even if the threads are running the same bit of code, they can disagree on the order of events because of operations in other threads in the absence of explicit ordering constraints, because the different CPU caches and internal buffers can hold different values for the same memory. It’s so important I’ll say it again: threads don’t have to agree on the order of events.
Not only do you have to throw out mental models based on interleaving operations, you also have to throw out mental models based on the idea of the compiler or processor reordering the instructions.
假设我们看到的代码没有重新排序。
示例代码:
#include <atomic>
#include <thread>
#include <assert.h>
std::atomic<bool> x,y;
std::atomic<int> z;
void write_x_then_y()
{
x.store(true,std::memory_order_relaxed); // 1
y.store(true,std::memory_order_relaxed); // 2
}
void read_y_then_x()
{
while(!y.load(std::memory_order_relaxed)); // 3
if(x.load(std::memory_order_relaxed)) // 4
++z;
}
int main() {
x=false;
y=false;
z=0;
std::thread a(write_x_then_y);
std::thread b(read_y_then_x);
a.join();
b.join();
assert(z.load()!=0); // 5
}
来自这个 link:https://www.developerfusion.com/article/138018/memory-ordering-for-atomic-operations-in-c0x/
为什么 x.load(relaxed)
return false
但 y.load(relaxed)
return true
?
作者总结
This time the assert (5) can fire, because the load of x (4) can read false, even though the load of y (3) reads true and the store of x (1) happens-before the store of y (2). x and y are different variables, so there are no ordering guarantees relating to the visibility of values arising from operations on each.
问。为什么 x 的负载可以是假的?
作者得出断言可以触发的结论。所以,z
可以是 0
。
所以,if(x.load(std::memory_order_relaxed))
: x.load(std::memory_order_relaxed)
是 false
.
但无论如何,while(!y.load(std::memory_order_relaxed));
使得 y
true
.
如果我们不重新排序(1)和(2)的代码序列,怎么可能y
为真而x
仍然不是存储?
如何理解作者提供的图?
基于 the store of x (1) happens-before the store of y (2)
,如果 x.store(relaxed)
发生在 y.store(relaxed)
之前,x
现在应该是 true
。但是为什么 x
仍然是 false
即使 y
是 true
?
您和一位朋友都同意 x=false
和 y=false
。有一天,你寄给他一封信,告诉他x=true
。第二天你给他发了一封信告诉他y=true
。你肯定按正确的顺序给他发信。
一段时间后,您的朋友收到您的来信,说 y=true
。现在您的朋友对 x
了解多少?他可能已经收到通知他的信x=true
。但也许邮政系统暂时丢失了它,他明天就会收到。所以对他来说,x=false
和x=true
都是他收到y=true
信件的有效可能性。
所以,回到硅世界。线程之间的内存 根本无法保证 其他线程的写入以任何特定顺序出现,因此 'delayed x' 完全有可能。所有添加 atomic
和使用 relaxed
所做的都是阻止两个线程在单个变量上竞争成为未定义的行为。它 根本无法保证 订购。 那就是强排序的目的。
或者,稍微粗暴一点,看我的MSPaint技巧:
在这种情况下,'x' 从第一个线程流向第二个线程的紫色箭头来得太晚,而绿色箭头(y 交叉)发生得很快。
If we don't reorder the code sequence of (1) and (2), how is it possible that y is true but x is still not be stored?
答案部分在第一个引用中:
because the different CPU caches and internal buffers can hold different values for the same memory.
换句话说,其他线程可以看到陈旧的值。因此可以存储 x 和 y,但尚未传播到其他线程。并且缓存传播顺序可能与其存储顺序不同。
例如,三个线程,第一个修改 x 和 y,缓存以不同的顺序传播到不同的线程:
x == 0 x == 0 x == 0
y == 0 y == 0 y == 0
+-------+ +-------+
| x = 1 | ----> | x = 1 |
+-------+ +-------+
+-------+ +-------+
| y = 1 | -------------------> | y = 1 |
+-------+ +-------+
x == 1 x == 1 x == 0
y == 1 y == 0 y == 1
我阅读了很多帖子并观看了几个 Youtube 视频 C++ 原子和内存模型(ConCpp 17、14)。
当我阅读 Concurrency In Action 一书时,第 5.3.3 节 RELAXED ORDERING,我仍然无法理解提供的示例作者在他的假设下。
作者的假设
It’s not just that the compiler can reorder the instructions. Even if the threads are running the same bit of code, they can disagree on the order of events because of operations in other threads in the absence of explicit ordering constraints, because the different CPU caches and internal buffers can hold different values for the same memory. It’s so important I’ll say it again: threads don’t have to agree on the order of events. Not only do you have to throw out mental models based on interleaving operations, you also have to throw out mental models based on the idea of the compiler or processor reordering the instructions.
假设我们看到的代码没有重新排序。
示例代码:
#include <atomic>
#include <thread>
#include <assert.h>
std::atomic<bool> x,y;
std::atomic<int> z;
void write_x_then_y()
{
x.store(true,std::memory_order_relaxed); // 1
y.store(true,std::memory_order_relaxed); // 2
}
void read_y_then_x()
{
while(!y.load(std::memory_order_relaxed)); // 3
if(x.load(std::memory_order_relaxed)) // 4
++z;
}
int main() {
x=false;
y=false;
z=0;
std::thread a(write_x_then_y);
std::thread b(read_y_then_x);
a.join();
b.join();
assert(z.load()!=0); // 5
}
来自这个 link:https://www.developerfusion.com/article/138018/memory-ordering-for-atomic-operations-in-c0x/
为什么 x.load(relaxed)
return false
但 y.load(relaxed)
return true
?
作者总结
This time the assert (5) can fire, because the load of x (4) can read false, even though the load of y (3) reads true and the store of x (1) happens-before the store of y (2). x and y are different variables, so there are no ordering guarantees relating to the visibility of values arising from operations on each.
问。为什么 x 的负载可以是假的?
作者得出断言可以触发的结论。所以,z
可以是 0
。
所以,if(x.load(std::memory_order_relaxed))
: x.load(std::memory_order_relaxed)
是 false
.
但无论如何,while(!y.load(std::memory_order_relaxed));
使得 y
true
.
如果我们不重新排序(1)和(2)的代码序列,怎么可能y
为真而x
仍然不是存储?
如何理解作者提供的图?
基于 the store of x (1) happens-before the store of y (2)
,如果 x.store(relaxed)
发生在 y.store(relaxed)
之前,x
现在应该是 true
。但是为什么 x
仍然是 false
即使 y
是 true
?
您和一位朋友都同意 x=false
和 y=false
。有一天,你寄给他一封信,告诉他x=true
。第二天你给他发了一封信告诉他y=true
。你肯定按正确的顺序给他发信。
一段时间后,您的朋友收到您的来信,说 y=true
。现在您的朋友对 x
了解多少?他可能已经收到通知他的信x=true
。但也许邮政系统暂时丢失了它,他明天就会收到。所以对他来说,x=false
和x=true
都是他收到y=true
信件的有效可能性。
所以,回到硅世界。线程之间的内存 根本无法保证 其他线程的写入以任何特定顺序出现,因此 'delayed x' 完全有可能。所有添加 atomic
和使用 relaxed
所做的都是阻止两个线程在单个变量上竞争成为未定义的行为。它 根本无法保证 订购。 那就是强排序的目的。
或者,稍微粗暴一点,看我的MSPaint技巧:
在这种情况下,'x' 从第一个线程流向第二个线程的紫色箭头来得太晚,而绿色箭头(y 交叉)发生得很快。
If we don't reorder the code sequence of (1) and (2), how is it possible that y is true but x is still not be stored?
答案部分在第一个引用中:
because the different CPU caches and internal buffers can hold different values for the same memory.
换句话说,其他线程可以看到陈旧的值。因此可以存储 x 和 y,但尚未传播到其他线程。并且缓存传播顺序可能与其存储顺序不同。
例如,三个线程,第一个修改 x 和 y,缓存以不同的顺序传播到不同的线程:
x == 0 x == 0 x == 0
y == 0 y == 0 y == 0
+-------+ +-------+
| x = 1 | ----> | x = 1 |
+-------+ +-------+
+-------+ +-------+
| y = 1 | -------------------> | y = 1 |
+-------+ +-------+
x == 1 x == 1 x == 0
y == 1 y == 0 y == 1