Joe Duffy,给出了6 rules that describe the CLR 2.0+ memory model(这是实际的实施,而不是任何ECMA标准)我正在写下我试图解决这个问题的尝试,主要是作为一种橡皮避短的方式,但是如果我犯了一个错误的话逻辑,至少有人能够在它引起我悲伤之前抓住它。
我试图了解这些规则。
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之外的所有规则。任何人都想启发我(或纠正我或添加上述任何内容?)
答案 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
更好,而不是使用本地,这会引入竞争条件。