【问题标题】:Understanding the Weak memory model了解弱内存模型
【发布时间】:2020-12-13 12:19:28
【问题描述】:

假设我们有两个线程,在内存中使用两个变量 AB

Thread 1       Thread 2
========       ========
1) A = 1       3) B = 1
2) Print(B)    4) Print(A)

我知道在Sequential consistent (strong) model 你会得到 1 -> 2 -> 3-> 4 按顺序执行。 x86 是TSO,它接近于一个强模型(但不如一个强)。

我不明白周模型是什么?弱模型只是选择随机指令并执行吗?即像4 -> 2 -> 3 -> 1 这样的事情是可能的吗?

关于这个话题我还有 2 个问题:

  • Out-of-order execution 由 CPU 完成以利用否则会被浪费的指令周期和memory reordering due to memory model 之间有什么区别?还是memory reordering 只处理Load/Store 指令?

  • memory model 只有在处理多个线程时才需要考虑吗?为什么在单线程程序中不是问题?

【问题讨论】:

  • Re, "...reordering due to memory model..." 我从来没有听说过任何需要指令的内存模型重新排序。但是许多内存模型允许它。这就是内存模型的全部意义所在:足够宽容,以便可以在不同的硬件平台上有效地实现,并进行足够详细的描述/指定,以便程序员可以预测和处理后果。
  • Re,“我知道在顺序一致(强)模型中,您将按顺序执行 1 -> 2 -> 3-> 4。”这听起来好像您在说强顺序一致性要求线程 1 在线程 2 可以启动之前运行完成。这听起来根本不对。我在这里有点超出我的专业领域,但是在没有任何同步的情况下,我看不出保证不同线程执行的操作之间的任何时间关系有什么意义。线程的全部意义在于,它们可以被允许在大多数时间彼此独立运行。

标签: multithreading memory-barriers memory-model


【解决方案1】:

顺序一致性根本不会告诉你它会执行 1,2,3,4。

顺序一致性告诉你如果CPU0执行1,2,CPU1执行3,4; CPU 将按该顺序执行块,并且在 1 之前不会感知到 2 的副作用(内存存储);并且在 3 之前不会感觉到 4 的副作用。

如果早于A=B=0,则:

Thread 1       Thread 2
========       ========
1) A = 1       3) B = 1
2) Print(A,B)  4) Print(A,B)

所有顺序并发告诉我们可能的输出是:

Thread 1 { 1, 0 }, { 1, 1}
Thread 2 { 0, 1 }, { 1, 1}.

如果我们将其扩展到A=B=C=D=0的初始状态

Thread 1       Thread 2
========       ========
A = 1          D = 1
C = 1          B = 1
Print(A,B,C,D) Print(A,B,C,D)

Thread1 有效输出:

1: {1, 0, 1, 0}       -- no effects from thread2 seen
2: {1, 0, 1, 1}       -- update of D visible; not B
3: {1, 1, 1, 0}       -- update of B visible; not D
4: {1, 1, 1, 1}       -- update of B and D visible.

Thread2 有效输出:

5: {0, 1, 0, 1}       -- no effects from thread1 seen
6: {0, 1, 1, 1}       -- update of C visible; not A
7: {1, 1, 0, 1}       -- update of A visible; not C
8: {1, 1, 1, 1}       -- update of A and C visible.

在顺序一致性中,1,2,4 : 5,6,8 是可能的。 在较弱的一致性中,1,2,3,4 : 5,6,7,8 是可能的。 请注意,在这两种情况下,线程都不会按顺序查看自己的更新;但输出 3,7 是线程看到其他线程无序更新的结果。

如果您需要维护特定的顺序,则插入障碍指令[1] 是首选方法。当 cpu 遇到屏障时,它会影响预取(读取屏障)、存储队列(写入屏障)或两者(rw 屏障)。

当有两个内存写入时:A = 1; C = 1; 你可以安装写屏障为membar w; store A; store C。这确保了所有商店之前到 A 的商店将在商店到 A 或 C 之前被看到;但不强制 A 和 C 之间的排序。

您可以将它们安装为store A; membar w; store C,这样可以确保在C 之前看到A 的商店; store A; store C; membar w 确保在任何后续商店之前都可以看到 A 和 C。

那么哪种障碍或障碍组合适合您的情况?

[1] 更现代的架构将障碍纳入加载和存储指令本身;所以你可能有一个store.sc A; store C;。这里的好处是限制了存储屏障的范围,这样存储单元只需序列化这些存储,而不是承受整个队列的延迟。

【讨论】:

猜你喜欢
  • 2011-06-03
  • 1970-01-01
  • 2011-02-25
  • 1970-01-01
  • 2013-06-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-04
相关资源
最近更新 更多