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

tha*_*ler 11 .net c# multithreading compiler-optimization

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

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

请考虑以下简化示例:

class Example {
    private Action _action;

    private void InvokeAction() {
        var local = this._action;
        if (local != null) {
            local();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

请在示例中忽略读取_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.
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

xan*_*tos 7

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

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

了解低锁技术在多线程应用程序中的影响

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

第2点:

不能引入读写操作.

解释如下:

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

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

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

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


mgr*_*ber 2

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

然而,我认为当前 Microsoft 的 CLR 实现并没有优化局部变量。

  • CLR via C#,第 264-265 页支持了这一点,两者都有不同的代码示例和有关它们的讨论。它还指出“所有 Microsoft 的 JIT 编译器都遵循不向堆内存引入新读取的不变性,因此,在局部变量中缓存引用可确保堆引用仅被访问一次”。 (3认同)