了解CLR 2.0内存模型

时间:2010-05-31 03:39:37

标签: c# .net multithreading memory lock-free

Joe Duffy,给出了6 rules that describe the CLR 2.0+ memory model(这是实际的实施,而不是任何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是易失性的,那么在加载y之前加载0不能移动,否则它可能会移动。挥发性商店似乎没有任何特殊属性,没有商店可以相互重新订购(这是一个非常有力的保证!)

完全障碍就像沙子中的一条线,装载和存储不能移动。

不知道规则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易变?我没有看到禁止执行上述优化的规则中的任何内容。这并不违反双重检查锁定,因为两个相同条件之间的锁定()会阻止负载移动到相邻位置,并且根据规则6,这是唯一可以被消除的时间。

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

1 个答案:

答案 0 :(得分:10)

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

  

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

MyObject mo = ...;
int f = mo.field;
if (f == 0)
{
    // do something
    Console.WriteLine(f);
}
  如果是初始之间的一段时间   将mo.field读入变量f和   随后使用f中的   Console.WriteLine足够长了,一个   编译器可能会决定它会更多   有效地重读mo.field两次。   ......如果这样做,那将是一个问题   mo是一个堆对象和线程   同时写到mo.field。该   if-block可能包含假设的代码   读入f的值保持为0,并且   读取的介绍可能会破裂   这个假设。此外   禁止这是为了挥发   变量,.NET内存模型   禁止它用于普通变量   也指GC堆内存。

blogged about one important place where this matters:举起活动的标准模式。

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

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

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