是否有用于防止该NullReferenceException异常常见的模式竞争条件? [英] Is there a race condition in this common pattern used to prevent NullReferenceException?

查看:112
本文介绍了是否有用于防止该NullReferenceException异常常见的模式竞争条件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我问 的问题,得到了this 有趣的(有点令人不安)的答案。



丹尼尔称他答案(除非我读不正确的话)的 ECMA-335 CLI 的规范可以让编译器生成抛出一个的NullReferenceException 从代码以下 DoCallback 方法。

  MyClass类{
私人行动_回电话;
公益行动回调{
{返回_callback; }
集合{_callback =价值; }
}
公共无效DoCallback(){
当地行动;
LOCAL =回拨;
如果(本地== NULL)
LOCAL =新的Action(()=> {});
本地();
}
}

他说,为了保证一个的NullReferenceException 不抛出,挥发性关键字应在 _callback


$ b;或锁定周围应行本地=回用$ b

任何人都可以证实吗?而且,如果这是真的,有没有行为的 .NET 对这个问题的编译器?



之间<差异p > 修改

这里的标准



更新

我想这是规范的相关部分(12.6.4):




在CLI中符合实现可以自由使用任何技术来执行程序
这保证,
执行的一个单一的线程内,由一个线程生成的副作用和例外是
由CIL指定的顺序显示。为此目的,仅
挥发性操作(包括易失性读)构成可见
副作用。 (请注意,虽然仅挥发性操作构成
可见的副作用,易失性操作还影响非易失引用的能见度$ B $湾)挥发性操作在
§12.6.7指定。有注入线程相对于异常
无顺序保证被另一个线程(这些例外
有时被称为异步异常(如
System.Threading.ThreadAbortException)。



[理由:一个优化
编译器可以自由地重新排序副作用和同步例外
中的程度,该重排序不改变任何可观察到的节目
行为结束理由]



[注:CLI的实现是
允许使用优化编译器,例如,转换CIL
键机器码提供编译器保持相同的顺序的副作用和
同步异常(各
执行单一线程内)。




所以...我很好奇,这个语句是否允许编译器优化回调属性(访问简单的现场)和本地变量产生以下,这在执行单线程相同的行为

 如果(_callback!= NULL)_callback(); 
,否则新的行动(()=> {})();



上的12.6.7节中的挥发性关键字似乎提供了希望避免优化程序员的解决方案:




一个挥发性读了收购语义,这意味着读为
保证之前,
在CIL指令序列读取指令后发生内存的任何引用发生。挥发性写
具有释放语义,意思是写保证之前在CIL
指令序列中的写指令的内存引用发生后,
。 CLI中的一致性实现应
担保挥发这样的操作语义。这将确保所有
线程将观察在
,他们执行的顺序任何其他线程执行的挥发性写入。但符合条件的实现是不提供挥发性写入从执行的所有线程看到
单总排序需要
。即
CIL转换为本地代码的优化编译器不得删除任何挥发性操作,也不得
它合并多种挥发性操作成一个单一的操作。



解决方案
通过C#的(第264-265),杰弗里里希特讨论了这个特定的问题,并承认

CLR <青霉>是的可能被换出的局部变量




[T]的他的代码可以由编译器优化完全删除本地[...]变量。如果发生这种情况,这个版本的代码是相同的[版本引用事件/回调直接两次],所以的NullReferenceException 仍然是可能的。




里氏建议使用 <$ C的$ C> Interlocked.CompareExchange< T> 来最终解决这个问题:

 公共无效DoCallback()
{
动作当地= Interlocked.CompareExchange(REF _callback,NULL,NULL);
如果(地方!= NULL)
本地();
}



不过,里氏承认,微软刚刚在实时(JIT)编译器的的优化掉的本地变量;并且,虽然这可能在理论上,改变,它几乎可以肯定永远不会因为这会导致过多的应用程序打破作为结果



此问题已经提出而在允许C#在回答长度对局部变量编译器的优化,并从内存重新获取价值。确保通过 xanatox 和读答案的了解低锁技术在多线程应用影响一文引用它。既然你特别问了一下单,你要注意引用 [单声道?-dev]内存模型邮件列表消息:




现在我们提供接近的架构支持ECMA宽松的语义你正在运行。



I asked this question and got this interesting (and a little disconcerting) answer.

Daniel states in his answer (unless I'm reading it incorrectly) that the ECMA-335 CLI specification could allow a compiler to generate code that throws a NullReferenceException from the following DoCallback method.

class MyClass {
    private Action _Callback;
    public Action Callback { 
        get { return _Callback; }
        set { _Callback = value; }
    }
    public void DoCallback() {
        Action local;
        local = Callback;
        if (local == null)
            local = new Action(() => { });
        local();
    }
}

He says that, in order to guarantee a NullReferenceException is not thrown, the volatile keyword should be used on _Callback or a lock should be used around the line local = Callback;.

Can anyone corroborate that? And, if it's true, is there a difference in behavior between Mono and .NET compilers regarding this issue?

Edit
Here is a link to the standard.

Update
I think this is the pertinent part of the spec (12.6.4):

Conforming implementations of the CLI are free to execute programs using any technology that guarantees, within a single thread of execution, that side-effects and exceptions generated by a thread are visible in the order specified by the CIL. For this purpose only volatile operations (including volatile reads) constitute visible side-effects. (Note that while only volatile operations constitute visible side-effects, volatile operations also affect the visibility of non-volatile references.) Volatile operations are specified in §12.6.7. There are no ordering guarantees relative to exceptions injected into a thread by another thread (such exceptions are sometimes called "asynchronous exceptions" (e.g., System.Threading.ThreadAbortException).

[Rationale: An optimizing compiler is free to reorder side-effects and synchronous exceptions to the extent that this reordering does not change any observable program behavior. end rationale]

[Note: An implementation of the CLI is permitted to use an optimizing compiler, for example, to convert CIL to native machine code provided the compiler maintains (within each single thread of execution) the same order of side-effects and synchronous exceptions.

So... I'm curious as to whether or not this statement allows a compiler to optimize the Callback property (which accesses a simple field) and the local variable to produce the following, which has the same behavior in a single thread of execution:

if (_Callback != null) _Callback();
else new Action(() => { })();

The 12.6.7 section on the volatile keyword seems to offer a solution for programmers wishing to avoid the optimization:

A volatile read has "acquire semantics" meaning that the read is guaranteed to occur prior to any references to memory that occur after the read instruction in the CIL instruction sequence. A volatile write has "release semantics" meaning that the write is guaranteed to happen after any memory references prior to the write instruction in the CIL instruction sequence. A conforming implementation of the CLI shall guarantee this semantics of volatile operations. This ensures that all threads will observe volatile writes performed by any other thread in the order they were performed. But a conforming implementation is not required to provide a single total ordering of volatile writes as seen from all threads of execution. An optimizing compiler that converts CIL to native code shall not remove any volatile operation, nor shall it coalesce multiple volatile operations into a single operation.

解决方案

In CLR via C# (pp. 264–265), Jeffrey Richter discusses this specific problem, and acknowledges that it is possible for the local variable to be swapped out:

[T]his code could be optimized by the compiler to remove the local […] variable entirely. If this happens, this version of the code is identical to the [version that references the event/callback directly twice], so a NullReferenceException is still possible.

Richter suggests the use of Interlocked.CompareExchange<T> to definitively resolve this issue:

public void DoCallback() 
{
    Action local = Interlocked.CompareExchange(ref _Callback, null, null);
    if (local != null)
        local();
}

However, Richter acknowledges that Microsoft’s just-in-time (JIT) compiler does not optimize away the local variable; and, although this could, in theory, change, it almost certainly never will because it would cause too many applications to break as a result.

This question has already been asked and answered at length in "Allowed C# Compiler optimization on local variables and refetching value from memory". Make sure to read the answer by xanatox and the "Understand the Impact of Low-Lock Techniques in Multithreaded Apps" article it cites. Since you asked specifically about Mono, you should pay attention to referenced "[Mono-dev] Memory Model?" mailing list message:

Right now we provide loose semantics close to ecma backed by the architecture you're running.

这篇关于是否有用于防止该NullReferenceException异常常见的模式竞争条件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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