14

Joe Duffy,给出了描述 CLR 2.0+ 内存模型的 6 条规则(它是实际实现,不是任何 ECMA 标准)按照我的逻辑,至少这里有人能够在它让我伤心之前抓住它。

  • 规则 1:永远不会违反加载和存储之间的数据依赖关系。
  • 规则 2:所有存储都有释放语义,即没有加载或存储可以在一个之后移动。
  • 规则 3:获取所有易失性负载,即任何负载或存储都不能移动到一个之前。
  • 规则 4:任何加载和存储都不能跨越全屏障(例如 Thread.MemoryBarrier、锁获取、Interlocked.Exchange、Interlocked.CompareExchange 等)。
  • 规则 5:可能永远不会引入堆的加载和存储。
  • 规则 6:只有在合并来自/到同一位置的相邻加载和存储时,才能删除加载和存储。

我试图理解这些规则。

x = y
y = 0 // Cannot move before the previous line according to Rule 1.

x = y
z = 0
// equates to this sequence of loads and stores before possible re-ordering
load y
store x
load 0
store z

看这个,似乎加载 0 可以移动到加载 y 之前,但商店可能根本不会重新排序。因此,如果一个线程看到 z == 0,那么它也会看到 x == y。

如果 y 是 volatile,则加载 0 不能在加载 y 之前移动,否则可能。不稳定的商店似乎没有任何特殊属性,没有商店可以相互重新订购(这是一个非常强的保证!)

完整的障碍就像沙中的一条线,装载和存储无法移动。

不知道第 5 条是什么意思。

我猜规则 6 意味着如果你这样做:

x = y
x = z

然后,CLR 可以删除对 y 的加载和对 x 的第一次存储。

x = y
z = y
// equates to this sequence of loads and stores before possible re-ordering
load y
store x
load y
store z
// could be re-ordered like this
load y
load y
store x
store z
// rule 6 applied means this is possible?
load y
store x // but don't pop y from stack (or first duplicate item on top of stack)
store z

如果 y 不稳定怎么办?我在规则中看不到任何禁止执行上述优化的内容。这并不违反双重检查锁定,因为两个相同条件之间的 lock() 可以防止负载移动到相邻位置,并且根据规则 6,这是唯一可以消除它们的时间。

所以我想我理解除了规则 5 之外的所有内容。任何人都想启发我(或纠正我或在上述任何内容中添加一些内容?)

4

1 回答 1

11

Joe Duffy 在 pp517-18 of Concurrent Programming on Windows中讨论了规则 5 :

作为可能引入负载的示例,请考虑以下代码:

MyObject mo = ...;
int f = mo.field;
if (f == 0)
{
    // do something
    Console.WriteLine(f);
}

如果从 mo.field 初始读取到变量 f 和随后在 Console.WriteLine 中使用 f 之间的时间段足够长,编译器可能会认为重新读取 mo.field 两次会更有效。...如果 mo 是堆对象并且线程同时写入 mo.field,那么这样做将是一个问题。if 块可能包含假设读入 f 的值保持为 0 的代码,并且读取的引入可能会打破这个假设。除了对 volatile 变量禁止此操作外,.NET 内存模型还禁止对引用 GC 堆内存的普通变量进行此操作。

在博客上写了一个重要的地方:引发事件的标准模式。

EventHandler handler = MyEvent;
if (handler != null)
    handler(this, EventArgs.Empty);

为了防止在单独线程上删除事件处理程序时出现问题,我们读取 的当前值,MyEvent并且仅在该委托为非空时才调用事件处理程序。

如果可以引入从堆读取,编译器/JIT 可能会决定MyEvent再次读取会更好,而不是使用会引入竞争条件的本地。

于 2010-05-31T04:13:13.233 回答