阅读介绍在C#中 - 如何防范呢? [英] Read Introduction in C# - how to protect against it?

查看:143
本文介绍了阅读介绍在C#中 - 如何防范呢?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在MSDN杂志的文章讨论阅读介绍的概念,并给出了code样品可通过它被打破。

An article in MSDN Magazine discusses the notion of Read Introduction and gives a code sample which can be broken by it.

public class ReadIntro {
  private Object _obj = new Object();
  void PrintObj() {
    Object obj = _obj;
    if (obj != null) {
      Console.WriteLine(obj.ToString()); // May throw a NullReferenceException
    }
  }
  void Uninitialize() {
    _obj = null;
  }
}

请注意,这可能会抛出一个NullReferenceException的评论 - 我从来不知道这是可能的。

Notice this "May throw a NullReferenceException" comment - I never knew this was possible.

所以我的问题是:我怎么能防止读取介绍

So my question is: how can I protect against read introduction?

我也真的很感谢的解释恰好在编译器决定引入读,因为本文不包含它。

I would also be really grateful for an explanation exactly when the compiler decides to introduce reads, because the article doesn't include it.

推荐答案

让我尝试将它分解为澄清这一复杂的问题。

Let me try to clarify this complicated question by breaking it down.

什么是读引进?

查看简介是一个优化借此code:

"Read introduction" is an optimization whereby the code:

public static Foo foo; // I can be changed on another thread!
void DoBar() {
  Foo fooLocal = foo;
  if (fooLocal != null) fooLocal.Bar();
}

时,通过消除局部变量优化。编译器可以有理由相信的如果只有一个线程的再 fooLocal 是一样。编译器是明确允许进行任何优化,这将是不可见的在单个线程中,即使它成为在多线程的情况可见。因此,允许编译器重写此为:

is optimized by eliminating the local variable. The compiler can reason that if there is only one thread then foo and fooLocal are the same thing. The compiler is explicitly permitted to make any optimization that would be invisible on a single thread, even if it becomes visible in a multithreaded scenario. The compiler is therefore permitted to rewrite this as:

void DoBar() {
  if (foo != null) foo.Bar();
}

和现在有一个竞争条件。如果变成非空到空的检查后,那么很可能是读第二遍,和第二时间也可能是空,这将随后崩溃。从诊断故障转储的人的角度来看,这将是完全神秘的。

And now there is a race condition. If foo turns from non-null to null after the check then it is possible that foo is read a second time, and the second time it could be null, which would then crash. From the perspective of the person diagnosing the crash dump this would be completely mysterious.

可这居然会发生?

当你链接的文章,叫了一声:

As the article you linked to called out:

请注意,您将无法重现使用这种code样品的NullReferenceException在.NET Framework 4.5基于x86的64位。阅读介绍是非常困难的,在.NET框架4.5重现,但它仍然会出现在某些特殊情况。

Note that you won’t be able to reproduce the NullReferenceException using this code sample in the .NET Framework 4.5 on x86-x64. Read introduction is very difficult to reproduce in the .NET Framework 4.5, but it does nevertheless occur in certain special circumstances.

在x86 / x64芯片有一个强有力的内存模型和JIT编译器是没有侵略性在这一领域;他们不会做这种优化。

x86/x64 chips have a "strong" memory model and the jit compilers are not aggressive in this area; they will not do this optimization.

如果你刚好是在一个弱内存模型的处理器上运行您的code,像ARM的芯片,则全盘皆输。

If you happen to be running your code on a weak memory model processor, like an ARM chip, then all bets are off.

当你说编译的编译器是什么意思?

When you say "the compiler" which compiler do you mean?

我的意思是JIT编译器。 C#编译器从未介绍读取这种方式。 (这是允许的,但在实践中它从不这样做)

I mean the jit compiler. The C# compiler never introduces reads in this manner. (It is permitted to, but in practice it never does.)

这难道不是一种不好的做法,在线程之间共享内存无记忆障碍是什么?

Isn't it a bad practice to be sharing memory between threads without memory barriers?

是的。东西应该在这里完成引进内存屏障,因为 的价值可能已经是一个过时的缓存值在处理器缓存的。我的preference用于引入一个存储器屏障是使用一个锁。您也可以使该领域挥发性,或使用 VolatileRead ,或使用的互锁1 的方法。所有这些的引入内存屏障。 (挥发性介绍只有半护栏仅供参考。)

Yes. Something should be done here to introduce a memory barrier because the value of foo could already be a stale cached value in the processor cache. My preference for introducing a memory barrier is to use a lock. You could also make the field volatile, or use VolatileRead, or use one of the Interlocked methods. All of those introduce a memory barrier. (volatile introduces only a "half fence" FYI.)

仅仅因为有一个记忆障碍并不意味着不进行优化,读的介绍。然而,抖动远不如积极地追求影响code,其中包含一个内存屏障优化。

Just because there's a memory barrier does not necessarily mean that read introduction optimizations are not performed. However, the jitter is far less aggressive about pursuing optimizations that affect code that contains a memory barrier.

还有没有其他的危险,这种模式?

Are there other dangers to this pattern?

当然!让我们假设有没有读介绍。 您仍然有竞争状态。如果另一个线程集来检查后空,,并同时修改全局状态酒吧是怎么回事消耗?现在你有两个线程,其中之一认为,不为null,全局状态是正常的呼叫酒吧,而另一个线程其认为的相反,你正在运行酒吧。这是一个灾难。

Sure! Let's suppose there are no read introductions. You still have a race condition. What if another thread sets foo to null after the check, and also modifies global state that Bar is going to consume? Now you have two threads, one of which believes that foo is not null and the global state is OK for a call to Bar, and another thread which believes the opposite, and you're running Bar. This is a recipe for disaster.

那么,什么是最好的做法在这里?

So what's the best practice here?

首先,不共享跨线程内存的。这整个想法,还有你的程序为主线的内部人控制的两个线程只是疯狂的开始。它不应该已经摆在首位的事情。使用线程的轻量级进程;给他们一个独立的任务来执行,不与程序在所有的主线内存互动,只是用它们来种田了计算密集型工作。

First, do not share memory across threads. This whole idea that there are two threads of control inside the main line of your program is just crazy to begin with. It never should have been a thing in the first place. Use threads as lightweight processes; give them an independent task to perform that does not interact with the memory of the main line of the program at all, and just use them to farm out computationally intensive work.

第二,如果你要共享跨线程内存中,然后的使用锁能够连续访问内存的。锁是便宜的,如果他们不争辩,如果你有争夺,然后解决这个问题。低锁和无锁的解决方案是非常难以得到的权利。

Second, if you are going to share memory across threads then use locks to serialize access to that memory. Locks are cheap if they are not contended, and if you have contention, then fix that problem. Low-lock and no-lock solutions are notoriously difficult to get right.

第三,如果你要共享跨线程内存和你打电话,每一个方法涉及到共享内存必须是强劲的竞争条件的脸孔,还是种族必须淘汰。这是一个沉重的负担来承担,这就是为什么你不应该在第一时间去那里。

Third, if you are going to share memory across threads then every single method you call that involves that shared memory must either be robust in the face of race conditions, or the races must be eliminated. That is a heavy burden to bear, and that is why you shouldn't go there in the first place.

我的观点是:看介绍是可怕的,但坦率地说,他们是你最担心,如果你正在写code跨线程愉快地共享内存。这里有一千零一种其他的事情首先担心。

My point is: read introductions are scary but frankly they are the least of your worries if you are writing code that blithely shares memory across threads. There are a thousand and one other things to worry about first.

这篇关于阅读介绍在C#中 - 如何防范呢?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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