方法内联优化会导致竞态条件吗? [英] Can method inlining optimization cause race conditions?

查看:96
本文介绍了方法内联优化会导致竞态条件吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在此问题中看到:
使用扩展方法引发C#事件-不好吗?

我正在考虑使用此扩展方法安全地引发事件:

I'm thinking of using this extension method to safely raise an event:

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

但是Mike Rosenblum在Jon Skeet的回答中提出了这一担忧:

But Mike Rosenblum raise this concern in Jon Skeet's answer:


你们需要在这些扩展方法
中添加
[MethodImpl(MethodImplOptions.NoInlining)]
属性,否则您尝试将
代表复制到一个临时变量的操作可以通过JITter来优化
,而
允许使用空引用
例外。

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的情况下是否可以获得竞争条件:

I did some test in Release mode to see if I could get a race condition when the extension method is not marked with 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。

I ran the test for a while in Release mode and never had a NullReferenceException.

那么,迈克·罗森布拉姆(Mike Rosenblum)的评论是否错误,内联方法不能导致比赛条件?

So, was Mike Rosenblum wrong in his comment and method inlining cannot cause race condition?

事实上,我想这是真正的问题是的,SaifeRaise将内联为:

In fact, I guess the real question is, will SaifeRaise be inlined as:

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

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


推荐答案

问题不会一直内联该方法-无论是否内联,JITter都会通过内存访问来做一些有趣的事情。

The problem wouldn't have been inlining the method - it would have been the JITter doing interesting things with memory access whether or not it was inlined.

但是,我不相信它首先是一个问题。几年前,有人提出将其作为关注点,但我认为这被认为是对内存模型的错误阅读。变量只有一个逻辑读取,而JITter无法对其进行优化,以至于值在副本的一次读取和副本的第二次读取之间发生变化。

However, I don't believe it is an issue in the first place. It was raised as a concern a few years back, but I believe that was regarded as a flawed reading of the memory model. There's only one logical "read" of the variable, and the JITter can't optimise that away such that the value changes between one read of the copy and the second read of the copy.

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

Just to clarify, I understand exactly why this is causing a problem for you. You've basically got two threads modifying the same variable (as they're using captured variables). It's perfectly possible for the code to occur like this:

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!

在此代码中,这与通常的情况相比不太明显,因为变量是捕获的变量,而不是正常的静态/实例字段。

This is slightly less obvious in this code than normally, as the variable is a captured variable rather than a normal static/instance field. The same principle applies though.

安全提升方法的重点是将引用存储在不能修改的本地变量中。

The point of the safe raise approach is to store the reference in a local variable which can't be modified from any other threads:

EventHandler handler = myEvent;
if (handler != null)
{
    handler(null, EventArgs.Empty);
}

现在,线程2是否更改<$ c的值都没有关系$ c> myEvent -它无法更改处理程序的值,因此不会获得 NullReferenceException

Now it doesn't matter whether thread 2 changes the value of myEvent - it can't change the value of handler, so you won't get a NullReferenceException.

如果JIT做 内联 SafeRaise ,它将被内联到此代码段-因为内联参数最终作为新的局部变量。仅当JIT 错误地通过保留两个单独的 myEvent 读取来内联它时,问题才会出现。

If the JIT does inline SafeRaise, it will be inlined to this snippet - because the inlined parameter ends up as a new local variable, effectively. The problem would only be if the JIT incorrectly inlined it by keeping two separate reads of myEvent.

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

Now, as to why you only saw this happen in debug mode: I suspect that with the debugger attached, there's far more room for threads to interrupt each other. Possibly some other optimisation occurred - but it didn't introduce any breakage, so that's okay.

这篇关于方法内联优化会导致竞态条件吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆