内存重新排序会导致C#访问未分配的内存吗?

Dou*_*las 13 .net c# multithreading memory-barriers

据我所知,C#是一种安全的语言,除了通过unsafe关键字之外,不允许访问未分配的内存.但是,当线程之间存在不同步的访问时,其内存模型允许重新排序.这会导致竞争危险,在实例完全初始化之前,对新实例的引用似乎可用于竞赛线程,并且是双重检查锁定的众所周知的问题.Chris Brumme(来自CLR团队)在他们的Memory Model文章中解释了这一点:

考虑标准的双锁协议:

if (a == null)
{
    lock(obj)
    {
        if (a == null) 
            a = new A();
    }
}
Run Code Online (Sandbox Code Playgroud)

这是在典型情况下避免锁定读取'a'的常用技术.它在X86上运行得很好.但它将被ECMA CLI规范的合法但薄弱的实施所打破.确实,根据ECMA规范,获取锁具有获取语义并释放锁具有释放语义.

但是,我们必须假设在建造'a'期间已经发生了一系列商店.这些商店可以任意重新排序,包括将它们推迟到将新对象分配给'a'的出版商店之后的可能性.那时,在store.release之前有一个小窗口离开锁.在该窗口内,其他CPU可以在引用'a'中导航并查看部分构造的实例.

我一直对"部分构造的实例"的含义感到困惑.假设.NET运行时在分配而不是垃圾收集(讨论)时清除内存,这是否意味着另一个线程可能会读取仍包含垃圾收集对象数据的内存(如不安全语言中发生的情况)?

考虑以下具体示例:

byte[] buffer = new byte[2];

Parallel.Invoke(
    () => buffer = new byte[4],
    () => Console.WriteLine(BitConverter.ToString(buffer)));
Run Code Online (Sandbox Code Playgroud)

以上是竞争条件; 输出将是00-0000-00-00-00.但是,第二个线程是否有可能在数组的内存初始化为0 buffer 之前读取新引用,并输出一些其他任意字符串?

Eri*_*ert 14

我们不要在这里埋葬lede:你的问题的答案是否定的,你永远不会在CLR 2.0内存模型中观察到预先分配的内存状态.

我现在将解决你的几个非中心点.

我的理解是,C#是一种安全的语言,除了通过unsafe关键字之外,不允许访问未分配的内存.

这或多或少是正确的.有一些机制可以使用unsafe- 通过非托管代码,或者通过滥用结构布局来访问虚假内存.但总的来说,是的,C#是内存安全的.

但是,当线程之间存在不同步的访问时,其内存模型允许重新排序.

再次,这或多或少是正确的.考虑它的一个更好的方法是C#允许在任何点重新排序,其中重新排序对单个线程程序是不可见的,受到某些约束.这些约束包括在某些情况下引入获取和释放语义,并在某些关键点保留某些副作用.

Chris Brumme(来自CLR团队)......

已故伟大的克里斯的文章是宝石,并提供了很多洞察CLR的早期,但我注意到自2003年撰写该文章以来,内存模型已经有所增强,特别是关于你的问题提高.

克里斯是正确的,双重检查锁是非常危险的.有一种正确的方法可以在C#中进行双重检查锁定,稍微离开它时,你就会陷入可怕的错误的杂草中,只能在弱内存模型硬件上进行复制.

这是否意味着另一个线程可能会读取仍包含垃圾收集对象数据的内存

我认为你的问题不是关于克里斯所描述的旧的弱ECMA记忆模型,而是关于今天实际做出的保证.

重新排序不可能暴露先前的对象状态.您可以保证,当您读取新分配的对象时,其字段全部为零.

这是因为所有写入在当前存储器模型中都具有释放语义; 看到这个细节:

http://joeduffyblog.com/2007/11/10/clr-20-memory-model/

将存储器初始化为零的写入将不会相对于稍后的读取向前移动.

我一直对"部分构造的物体"感到困惑

Joe在这里讨论:http://joeduffyblog.com/2010/06/27/on-partiallyconstructed-objects/

这里关注的不是我们可能会看到对象的预分配状态.相反,这里的关注点是一个线程可能在构造函数仍在另一个线程上运行时看到一个对象.

实际上,构造函数终结器有可能同时运行,这非常奇怪!由于这个原因,终结器很难正确写入.

换句话说: CLR保证您将保留其自己的不变量.CLR的不变量是观察到新分配的内存被清零,因此将保留不变量.

但是,CLR是不是在维护的业务不变!如果你有一个构造函数来保证该字段xtrueif且仅当y是非null,那么有责任确保始终观察到该不变量为真.如果this两个线程以某种方式观察到,那么其中一个线程可能会观察到违反的不变量.

  • @Douglas:例如,考虑ASP.NET.是的,如果你需要,它支持进程隔离,但是一个设计方案是某些托管公司托管coke.com和pepsi.com,两者的后端代码可以在同一个进程中运行.如果一个网站在安全子集中​​运行的方式可以看到另一个网站的尚未清理的垃圾,那将是头版新闻; CLR不应该是新的Heartbleed向量.:-) (2认同)