通过 CALLED 方法中的 lock 语句保护通过 `ref` 参数访问字段吗?

Is access to fields VIA `ref` parameters guarded by lock statements in the CALLED method?

给定,

private object _x;

private object LoadAndSet(ref object x) {
   // lock established over read and update
   lock (somePrivateObjectNotUsedElsewhereThatIsIrrelvantToTheQuestion) {
       if (x == null)
           x = Load();
       return x;
   }
}

调用为,

public object Load() {
    return LoadAndSet(ref _x);
}

也就是说,第一个代码是不是相当于下面直接使用字段的代码? (分配直接发生,而不是通过 ref 参数。)

private object _x;

private object LoadAndSetFieldDirectly() {
   // lock established over read and update
   lock (somePrivateObjectNotUsedElsewhereThatIsIrrelvantToTheQuestion) {
       if (_x == null)
           _x = Load();
       return _x;
   }
}

public object Load() {
    return LoadAndSetFieldDirectly();
}

我怀疑这是真的,因为在方法的 MSIL 中使用了 ldind.refstind.ref;然而,问题是在编写这样的 ref 代码时请求权威 documentation/information 线程安全(或缺乏)。

lock(lockObject) { statements } 的语义是:

  • 任何两个不同的线程都不会位于使用同一个 lockObject 实例锁定的锁定代码区域中。如果一个线程在该区域中,那么它要么进入无限循环,正常完成,要么抛出异常。 (或者,对于高级玩家,通过 Monitor.Wait 进入等待状态;这不是关于正确使用监视器对象的教程。)第二个线程不会进入受保护区域,直到控制离开区域。
  • 锁中或锁后变量的读取不会"backwards in time"移动到锁前。
  • 锁中或锁之前的变量写入不会被移动"forwards in time"到锁之后。

(这是一个快速的非正式摘要;有关 C# 规范对读取和写入重新排序的保证的确切详细信息,请参阅规范。)

这些语义由运行时强制执行无论在锁体中访问的变量是字段、局部变量、普通形式参数、ref/out形式参数、数组元素还是指针解引用.

也就是说,有三件事让我对您的代码感到紧张。

首先,这是对现有机制的不必要且次优的重新发明。如果要实现惰性初始化,使用Lazy<T>。当您可以使用专家编写的代码,而这些专家已经从中获得了每一点性能时,为什么要自己动手并冒着出错的风险?

其次,您必须确保每个 字段的使用都处于锁定状态,而不仅仅是它的初始化。将字段作为 ref 参数传递会为该字段创建一个 别名 ,现在您已经完成了验证您是否在锁定 更难.

第三,这里似乎有机会进行不必要的争论。如果两个 different 字段都由两个不同线程上的相同代码初始化怎么办?现在他们在不需要的时候争夺锁。