给出
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
代码时,请乞求有关线程安全(或缺乏线程安全性)的权威文档/信息。
答案 0 :(得分:3)
lock(lockObject) { statements }
的语义是:
Monitor.Wait
进入等待状态;这并不是有关如何正确使用监视器对象的教程。)第二个线程只有在控制离开该线程后才进入受保护区域。地区。(这是一个简短的非正式摘要;有关C#规范所保证的读写顺序的确切详细信息,请参见该规范。)
这些语义由运行时强制执行,无论在锁体内访问的变量是字段,局部变量,普通形式参数,ref / out形式参数,数组元素还是指针取消引用。 >
也就是说,三件事让我对您的代码感到紧张。
首先,对现有机制进行不必要且次优的重新发明。如果要实现延迟初始化,请使用Lazy<T>
。当您可以使用已经精通性能的专家编写的代码时,为什么要自己动手并冒险出错呢?
第二,您必须确保对该字段的每次使用处于锁定状态,而不仅仅是对其进行初始化。将字段作为ref
参数传递会为该字段提供 alias ,现在您已经完成了验证是否已将所有使用该字段的锁置于 harder <下的工作。 / em>。
第三,这里似乎有机会进行不必要的争用。如果两个 different 字段都由同一代码在两个不同的线程上初始化,该怎么办?现在他们不需要时就争夺锁。