通过CALLED方法中的锁语句可以保护对VIA`ref`参数的字段访问吗?

时间:2018-09-18 22:46:30

标签: c# thread-safety ref

给出

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);
}
  • 字段_x)的读/写是“通过引用传递”的内容,在{{1} }?

也就是说,第一个代码是否等同于以下代码,其中直接使用该字段? (分配直接进行,而不是通过lock参数进行。)

ref

由于该方法的MSIL中使用了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(); } ldind.ref,我怀疑这是正确的;但是,问题是在编写这样的stind.ref代码时,请乞求有关线程安全(或缺乏线程安全性)的权威文档/信息。

1 个答案:

答案 0 :(得分:3)

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

  • 在使用同一lockObject实例锁定的锁定代码区域中,没有两个不同的线程。如果该区域中有一个线程,则它将陷入无限循环,正常完成或抛出。 (或者,对于高级玩家,则通过Monitor.Wait进入等待状态;这并不是有关如何正确使用监视器对象的教程。)第二个线程只有在控制离开该线程后才进入受保护区域。地区。
  • 锁中或锁之后的变量读取不会在时间上向后移动到锁之前。
  • 锁中或锁之前的变量写不会“向前移动”到锁后。

(这是一个简短的非正式摘要;有关C#规范所保证的读写顺序的确切详细信息,请参见该规范。)

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

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

首先,对现有机制进行不必要且次优的重新发明。如果要实现延迟初始化,请使用Lazy<T>。当您可以使用已经精通性能的专家编写的代码时,为什么要自己动手并冒险出错呢?

第二,您必须确保对该字段的每次使用处于锁定状态,而不仅仅是对其进行初始化。将字段作为ref参数传递会为该字段提供 alias ,现在您已经完成了验证是否已将所有使用该字段的锁置于 harder <下的工作。 / em>。

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