通过 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);
}
- 读取/写入到字段(
_x
)"passed by reference"是否涵盖在atomicity/visibility lock
? 的保证
也就是说,第一个代码是不是相当于下面直接使用字段的代码? (分配直接发生,而不是通过 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.ref
和 stind.ref
;然而,问题是在编写这样的 ref
代码时请求权威 documentation/information 线程安全(或缺乏)。
lock(lockObject) { statements }
的语义是:
- 任何两个不同的线程都不会位于使用同一个 lockObject 实例锁定的锁定代码区域中。如果一个线程在该区域中,那么它要么进入无限循环,正常完成,要么抛出异常。 (或者,对于高级玩家,通过
Monitor.Wait
进入等待状态;这不是关于正确使用监视器对象的教程。)第二个线程不会进入受保护区域,直到控制离开区域。
- 锁中或锁后变量的读取不会"backwards in time"移动到锁前。
- 锁中或锁之前的变量写入不会被移动"forwards in time"到锁之后。
(这是一个快速的非正式摘要;有关 C# 规范对读取和写入重新排序的保证的确切详细信息,请参阅规范。)
这些语义由运行时强制执行无论在锁体中访问的变量是字段、局部变量、普通形式参数、ref/out形式参数、数组元素还是指针解引用.
也就是说,有三件事让我对您的代码感到紧张。
首先,这是对现有机制的不必要且次优的重新发明。如果要实现惰性初始化,使用Lazy<T>
。当您可以使用专家编写的代码,而这些专家已经从中获得了每一点性能时,为什么要自己动手并冒着出错的风险?
其次,您必须确保每个 字段的使用都处于锁定状态,而不仅仅是它的初始化。将字段作为 ref
参数传递会为该字段创建一个 别名 ,现在您已经完成了验证您是否在锁定 更难.
第三,这里似乎有机会进行不必要的争论。如果两个 different 字段都由两个不同线程上的相同代码初始化怎么办?现在他们在不需要的时候争夺锁。
给定,
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
)"passed by reference"是否涵盖在atomicity/visibilitylock
? 的保证
也就是说,第一个代码是不是相当于下面直接使用字段的代码? (分配直接发生,而不是通过 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.ref
和 stind.ref
;然而,问题是在编写这样的 ref
代码时请求权威 documentation/information 线程安全(或缺乏)。
lock(lockObject) { statements }
的语义是:
- 任何两个不同的线程都不会位于使用同一个 lockObject 实例锁定的锁定代码区域中。如果一个线程在该区域中,那么它要么进入无限循环,正常完成,要么抛出异常。 (或者,对于高级玩家,通过
Monitor.Wait
进入等待状态;这不是关于正确使用监视器对象的教程。)第二个线程不会进入受保护区域,直到控制离开区域。 - 锁中或锁后变量的读取不会"backwards in time"移动到锁前。
- 锁中或锁之前的变量写入不会被移动"forwards in time"到锁之后。
(这是一个快速的非正式摘要;有关 C# 规范对读取和写入重新排序的保证的确切详细信息,请参阅规范。)
这些语义由运行时强制执行无论在锁体中访问的变量是字段、局部变量、普通形式参数、ref/out形式参数、数组元素还是指针解引用.
也就是说,有三件事让我对您的代码感到紧张。
首先,这是对现有机制的不必要且次优的重新发明。如果要实现惰性初始化,使用Lazy<T>
。当您可以使用专家编写的代码,而这些专家已经从中获得了每一点性能时,为什么要自己动手并冒着出错的风险?
其次,您必须确保每个 字段的使用都处于锁定状态,而不仅仅是它的初始化。将字段作为 ref
参数传递会为该字段创建一个 别名 ,现在您已经完成了验证您是否在锁定 更难.
第三,这里似乎有机会进行不必要的争论。如果两个 different 字段都由两个不同线程上的相同代码初始化怎么办?现在他们在不需要的时候争夺锁。