我正在比较 Anthony Williams 所著的 C++ Concurrency in Action 和 本演讲。
我发现后者的一些内容与前者的解释不符,我想问一下如何解决这种明显的不一致。这个例子取自演讲
这里:
\\ in main thread
std::atomic<int> x(0), y(0)
\\ thread 1
int r1 = x.load(std::memory_order_relaxed);
y.store(r1, std::memory_order_relaxed);
\\thread 2
int r2 = y.load(std::memory_order_relaxed);
x.store(42, std::memory_order_relaxed);
在评论幻灯片时,发言者说我有 r1 = r2 = 42
的情况很好,因为他说,编译器可以自由地重新排序第二个线程的指令。在书中,第151页我发现
对不同变量的宽松操作可以自由重新排序 前提是他们遵守任何发生在他们之前的关系 (例如,在同一线程内)。
这似乎与(在同一线程内)编译器可以自由地重新排序宽松的内存操作的说法相矛盾。
此外,书的作者还用一个比喻来解释放松的记忆顺序:
为了理解它是如何工作的,想象每个变量都是一个男人 带记事本的小隔间。他的记事本上有一个值列表。你可以 打电话给他并要求他给你一个值,或者你可以告诉他 写下一个新值。如果你告诉他写下一个新值,他 将其写在列表的底部。如果你问他一个值,他 从列表中读出一个数字。 当你第一次和这个人说话时,如果你问他一个值,他 可能会从他当时在他的笔记本上的列表中为您提供任何值。如果 然后你向他询问另一个值,他可能会再次给你相同的值 或列表下方的值。他永远不会给你价值 从列表的上方开始。如果你告诉他写下一个数字并 然后向他询问一个值,他会给你 您告诉他写下的数字或列表中低于该数字的数字。(我认为这只是一个关于放松内存操作中涉及的原子变量的线程之间的缓存一致性的比喻)
然而,演讲的例子并不符合这个逻辑:一开始,隔间里的两个人(x 和 y)在他们的记事本中只有
x = {0}
和
y={0}
。然后,当他们被要求加载
x
和
y
的值时,他们如何辨别与
0
不同的值?
问题
我处理标准原子周围的方式是将我的单线程直觉留在门口,并问“什么可以阻止这种奇怪的情况发生?”。如果没有什么可以阻止它,那么理论上它可以发生。
我在这里没有看到任何阻碍
r1 = r2 = 42
的东西。
每个修改顺序必须与每个单独的线程一致,但由于在您的示例中每个原子每个线程访问一次,因此该规则也没有用。
https://preshing.com/20120710/memory-barriers-are-like-source-control-operations/ 对于大多数类型的重新排序有一个类比,即请求共享服务器在 git 上推送或拉取repo,其中客户端和服务器之间的网络管道就像一个存储缓冲区,它自然地引入了 StoreLoad 重新排序至少,因为CPU 希望尽早加载并延迟存储。 但是这里相关的重新排序是LoadStore:
与 #LoadLoad 和 #StoreStore 不同,#LoadStore 在源代码管理操作方面没有巧妙的隐喻。理解 #LoadStore 屏障的最佳方法很简单,就是指令重新排序。
“指令”重新排序并不总是会发生;
完成来执行 LoadStore 重新排序,只有在准备好之前确实尝试读取寄存器结果时才会停止。关键时刻是当加载将数据从一致性缓存中复制出来时(无论何时准备好,在加载执行后的任何时候),或者当存储将数据从存储缓冲区复制到一致性缓存中时,复制到该核心已获得独占所有权的行中(MESI 修改或独占)并且商店已从 OoO 执行中退休后,因此已知是非投机性的。 与加载不同,推测存储需要缓冲,这样它们就不会以错误推测的状态“感染”其他核心。