允许对局部变量进行C#编译器优化并从内存中重新获取值

编辑 :我在问两个线程在没有正确同步的情况下同时访问相同数据会发生什么(在此编辑之前,该点未明确表达)。

我有一个关于C#编译器和JIT编译器执行的优化的问题。

请考虑以下简化示例:

class Example { private Action _action; private void InvokeAction() { var local = this._action; if (local != null) { local(); } } } 

请在示例中忽略读取_action可能会产生缓存和过期值,因为没有volatile说明符,也没有任何其他同步。 那不是重点:)

是否允许编译器(或实际上是运行时的抖动)优化对局部变量的赋值,而是从内存中读取_action两次:

 class Example { private Action _action; private void InvokeAction() { if (this._action != null) { this._action(); // might be set to null by an other thread. } } } 

当并行赋值将字段_action设置为null时,可能会抛出NullReferenceException

当然,在这个例子中,这样的“优化”没有任何意义,因为将值存储在寄存器中并因此使用局部变量会更快。 但是在更复杂的情况下,是否可以保证无需重新读取内存中的值而按预期工作?

根据ECMA规范中定义的内存模型进行合法优化。 如果_action是易失性的,则内存模型将保证该值只读取一次,因此不会发生此优化。

但是,我认为当前微软的CLR实现并没有优化局部变量。

我会(部分地)说与mgronber相反:-) Aaaah ……最后我说的是同样的事情…只是我引用了一篇文章:-(我会给他一个+1。

这是ECMA规范下的法律优化,但它是.NET> = 2.0“规范”下的非法优化。

从了解低锁技术在multithreading应用程序中的影响

阅读此处强模型2:.NET Framework 2.0

第2点:

不能引入读写操作。

解释如下:

但是,该模型不允许引入读取,因为这意味着从内存中重新获取值,而在低锁定代码中,内存可能会发生变化。

但请注意同一页下的技巧1:避免某些读取的锁定

在使用ECMA模型的系统中,还有一个额外的微妙之处。 即使只将一个内存位置提取到局部变量中并且多次使用本地,每次使用可能具有不同的值! 这是因为ECMA模型允许编译器消除局部变量并在每次使用时重新获取位置。 如果同时发生更新,则每次获取可能具有不同的值。 使用volatile声明可以抑制此行为,但问题很容易被忽略。

如果你是在Mono下写作,你应该被告知至少在2008年之前它正在使用ECMA内存模型(或者他们在他们的邮件列表中写道)

使用C#7 ,您应该按如下方式编写示例,实际上IDE会将其建议为“简化”。 编译器将生成使用临时局部的代码,只能_action从主内存中读取_action的位置(无论它是否为null ),这有助于防止常见的种族显示OP的第二个例子,即_action被访问两次,并且可以被中间的另一个线程设置为null

 class Example { private Action _action; private void InvokeAction() { this._action?.Invoke(); } }