为什么在这里使用Thread.MemoryBarrier?

6
浏览MEF源代码时,我找到了这一段内容。请问为什么在锁内需要使用MemoryBarrier
整个方法如下:
public void SatisfyImportsOnce(ComposablePart part)
{
    this.ThrowIfDisposed();

    if (this._importEngine == null)
    {
        ImportEngine importEngine = new ImportEngine(this, this._compositionOptions);

        lock(this._lock)
        {
            if (this._importEngine == null)
            {
                Thread.MemoryBarrier();
                this._importEngine = importEngine;
                importEngine = null;
            }
        }
        if(importEngine != null)
        {
            importEngine.Dispose();
        }
    }
    this._importEngine.SatisfyImportsOnce(part);
}

有时候,似乎锁定不够。 - Thomas Ayoub
不了解更多上下文情况,无法回答这个问题。 - Matthew Watson
3
在具有弱内存模型的处理器上,它会产生FUD。因此,一些 Microsoft 程序员可能永远无法从驯服 Itanium 的经历中恢复过来。这确保了另一个线程在使用“_importEngine”引用时可以观察到完全构建的对象。在弱处理器上,该引用可能会在对象字段被写入之前被写入内存,因此另一个线程可以看到未初始化的字段值。这不是必需的,因为.NET 2.0已经解决了这个问题,而且在这里也不需要,因为锁已经暗示了内存屏障。 - Hans Passant
这可以用Lazy或LazyInitializer替换。 - usr
1个回答

1

Thread.MemoryBarrier可以防止Jitter/编译器为了代码优化而重新排序任何指令。

Joe Albahari的C#多线程编程一书中,他说:

  • 编译器、CLR或CPU可能会重新排列程序的指令以提高效率。
  • 编译器、CLR或CPU可能会引入缓存优化,使变量的赋值不会立即对其他线程可见。

在这个例子中,可能是importEngine或_importEngine的值被缓存,所有线程都必须立即通知有关更改的信息非常重要。

此外,在将importEngine分配给_importEngine之前,MemoryBarrier在这种情况下提供了importEngine的“新鲜度”保证。


1
但是锁也意味着内存屏障。在锁内部,importEngine_importEngine都不能被缓存(除非其他东西在没有锁定this._lock的情况下写入_importEngine)。 - Scott Chamberlain
@ScottChamberlain 你是正确的。这就是为什么我认为在这种情况下使用MemoryBarrier,可能有其他线程在没有使用_lock的情况下对_importEngine进行写入。 - Vlad Bezden
2
在CLI(CLR的标准化版本)中,对于双重检查锁定习语的基本空值检查是不足的。CLI的内存模型要求变量是volatile,或者使用显式屏障。CLR具有更强的内存模型,既不需要volatile也不需要屏障。该代码的作者可能没有意识到CLR的额外保证,或者像Jon Skeet一样,他们不愿依赖于它们。屏障的成本有限,因为一旦初始化和同步,后续的调用将完全跳过锁定内容。 - Kevin Cathcart
主要参考资料是msdn杂志文章cbrumm博客上的一篇文章 - Kevin Cathcart

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接