.net - 方法内联优化会导致竞争条件吗?

标签 .net race-condition inline-method

正如在这个问题中看到的:
Raising C# events with an extension method - is it bad?

我正在考虑使用这个扩展方法来安全地引发一个事件:

public static void SafeRaise(this EventHandler handler, object sender, EventArgs e)
{
    if (handler != null)
        handler(sender, e);
}

但是 Mike Rosenblum 在 Jon Skeet 的回答中提出了这个问题:

You guys need to add the [MethodImpl(MethodImplOptions.NoInlining)] attribute to these extension methods or else your attempt to copy the delegate to a temporary variable could be optimized away by the JITter, allowing for a null reference exception.



我在 Release 模式下做了一些测试,看看当扩展方法没有用 NoInlining 标记时我是否能得到竞争条件:
int n;
EventHandler myListener = (sender, e) => { n = 1; };
EventHandler myEvent = null;

Thread t1 = new Thread(() =>
{
    while (true)
    {
        //This could cause a NullReferenceException
        //In fact it will only cause an exception in:
        //    debug x86, debug x64 and release x86
        //why doesn't it throw in release x64?
        //if (myEvent != null)
        //    myEvent(null, EventArgs.Empty);

        myEvent.SafeRaise(null, EventArgs.Empty);
    }
});

Thread t2 = new Thread(() =>
{
    while (true)
    {
        myEvent += myListener;
        myEvent -= myListener;
    }
});

t1.Start();
t2.Start();

我在 Release模式下运行了一段时间的测试,但从未出现过 NullReferenceException。

那么,Mike Rosenblum 的评论和方法内联不会导致竞争条件是错误的吗?

事实上,我想真正的问题是,SaifeRaise 会被内联为:
while (true)
{
    EventHandler handler = myEvent;
    if (handler != null)
        handler(null, EventArgs.Empty);
}

或者
while (true)
{
    if (myEvent != null)
        myEvent(null, EventArgs.Empty);
}

最佳答案

问题不在于内联方法 - 无论是否内联,JITter 都会在内存访问方面做一些有趣的事情。

但是,我首先不认为这是一个问题。几年前有人提出这个问题,但我认为这被认为是对内存模型的错误解读。变量只有一次逻辑“读取”,JITter 无法优化它,从而使值在一次读取副本和第二次读取副本之间发生变化。

编辑:只是为了澄清,我完全理解为什么这会给你带来问题。您基本上有两个线程修改同一个变量(因为它们使用捕获的变量)。代码完全有可能像这样发生:

Thread 1                      Thread 2

                              myEvent += myListener;

if (myEvent != null) // No, it's not null here...

                              myEvent -= myListener; // Now it's null!

myEvent(null, EventArgs.Empty); // Bang!

在这段代码中,这比正常情况稍微不那么明显,因为该变量是一个捕获的变量,而不是一个普通的静态/实例字段。同样的原则也适用。

安全提升方法的要点是将引用存储在不能从任何其他线程修改的局部变量中:
EventHandler handler = myEvent;
if (handler != null)
{
    handler(null, EventArgs.Empty);
}

现在线程 2 是否更改 myEvent 的值并不重要- 它不能改变 handler 的值,所以你不会得到 NullReferenceException .

如果 JIT 内联 SafeRaise ,它将被内联到此代码段中 - 因为内联参数最终有效地作为新的局部变量。问题仅在于 JIT 错误 通过保持 myEvent 的两个单独读取来内联它.

现在,至于为什么您只在 Debug模式下看到这种情况发生:我怀疑连接调试器后,线程相互中断的空间要大得多。可能发生了一些其他优化 - 但它没有引入任何破损,所以没关系。

关于.net - 方法内联优化会导致竞争条件吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2327343/

相关文章:

.net - 如何使用 Ninject Conventions 库绑定(bind)到不是接口(interface)的基本类型?

c# - 无法在 WinForms .net 应用程序下的 WebView 中显示 127.0.0.1 页面

.net - 按国家/文化列出的常见日期(和时间)格式

java - Java中的内联

C 内联函数和内存使用

c# - 在 VS2012 和 VS2010 上运行相同的程序表现出不同的行为

PHP/SQL 错误 : Possible race condition?

java - Java 信号量的确定性如何保证?

java - 奇怪的比赛条件?