了解CLR 2.0内存模型

Elo*_*off 13 .net c# memory multithreading lock-free

Joe Duffy给出了描述CLR 2.0+内存模型的6条规则(它是实际的实现,而不是任何ECMA标准)我正在写下我试图解决这个问题的方法,主要是作为橡皮避短的一种方式,但如果我犯了一个错误在我的逻辑中,至少有人能够在它引起我悲伤之前抓住它.

  • 规则1:从不违反加载和存储之间的数据依赖性.
  • 规则2:所有商店都有发布语义,即没有加载或商店可能会在一个商店之后移动.
  • 规则3:获取所有易失性载荷,即没有载荷或储存可能在其之前移动.
  • 规则4:任何加载和存储都不能跨越全屏障(例如Thread.MemoryBarrier,lock acquire,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
Run Code Online (Sandbox Code Playgroud)

看看这个,看起来负载0可以在加载y之前向上移动,但是存储可能根本不会被重新排序.因此,如果一个线程看到z == 0,那么它也会看到x == y.

如果y是易失性的,那么加载0在加载y之前不能移动,否则它可能会移动.挥发性商店似乎没有任何特殊属性,没有商店可以相互重新订购(这是一个非常有力的保证!)

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

不知道规则5的含义.

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

x = y
x = z
Run Code Online (Sandbox Code Playgroud)

然后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
Run Code Online (Sandbox Code Playgroud)

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

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

Bra*_*ger 10

Joe Duffy讨论了Windows并发编程 pp517-18的规则5 :

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

MyObject mo = ...;
int f = mo.field;
if (f == 0)
{
    // do something
    Console.WriteLine(f);
}
Run Code Online (Sandbox Code Playgroud)

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

在博客上写了一个重要的地方:举办活动的标准模式.

EventHandler handler = MyEvent;
if (handler != null)
    handler(this, EventArgs.Empty);
Run Code Online (Sandbox Code Playgroud)

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

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