为什么C#不允许空值被locking?
C#不允许locking空值。 我想我可以在locking之前检查这个值是否为null,但是因为我没有locking它,另外一个线程可能会出现,并将值设为null! 我怎样才能避免这种竞争条件?
locking一个从不为空的值,例如
Object _lockOnMe = new Object(); Object _iMightBeNull; public void DoSomeKungFu() { if (_iMightBeNull == null) { lock (_lockOnMe) { if (_iMightBeNull == null) { _iMightBeNull = ... whatever ...; } } } }
还要小心避免这种双重locking的有趣的竞争条件:双重locking内存模型保证
你不能locking一个空值,因为CLR没有地方附加SyncBlock,这就是允许CLR通过Monitor.Enter / Exit(这是内部使用的lock
)同步访问任意对象,
这里有两个问题:
首先,不要locking一个null
对象。 这两个对象如何能够被区分是null
?
其次,要在multithreading环境中安全地初始化variables,请使用双重检查的locking模式:
if (o == null) { lock (lockObj) { if (o == null) { o = new Object(); } } }
这将确保另一个线程尚未初始化该对象,并可用于实现Singleton模式。
为什么C#不允许空值被locking?
保罗的答案是迄今唯一在技术上正确的答案 ,所以我会接受这一答案 。 这是因为.NET中的监视器使用附加到所有引用types的同步块。 如果你有一个null
的variables,那么它不是指任何对象,这意味着监视器不能访问一个可用的同步块。
我怎样才能避免这种竞争条件?
传统的方法是locking一个对象引用,你知道永远不会为null
。 如果您发现自己处于无法保证的状态,那么我会将这种非传统方法分类。 除非您更详细地描述可导致可空locking目标的特定情况,否则实际上我可以在此处提及更多。
你的问题的第一部分已经回答了,但是我想为你的问题的第二部分添加一些内容。
使用不同的对象执行locking更为简单,特别是在这种情况下。 这也解决了维护关键部分中的多个共享对象的状态的问题,例如员工列表和员工照片列表。
此外,当你必须获取原始types的锁时,这种技术也是有用的,例如int或者decimal等。
在我看来,如果你正在使用这个技巧,就像其他人所build议的一样,那么你不需要执行两次空检查。 例如在被接受的答案Cris中使用,如果条件两次真的没有任何区别,因为locking的对象是不同的,那么实际上是修改,如果你locking在一个不同的对象,然后执行第一个空检查是没有用的,浪费中央处理器。
我会build议下面的一段代码;
object readonly syncRootEmployee = new object(); List<Employee> employeeList = null; List<EmployeePhoto> employeePhotoList = null; public void AddEmployee(Employee employee, List<EmployeePhoto> photos) { lock (syncRootEmployee) { if (employeeList == null) { employeeList = new List<Employee>(); } if (employeePhotoList == null) { employeePhotoList = new List<EmployeePhoto>(); } employeeList.Add(employee); foreach(EmployeePhoto ep in photos) { employeePhotoList.Add(ep); } } }
在这里如果有人看到比赛情况,我不能在这里看到任何竞赛状况,请在评论中回应。 正如你在上面的代码中看到的那样,它立刻解决了3个问题,一个在locking之前不需要空的检查,其次是在不locking两个共享源的情况下创build临界区,第三个locking多个对象,由于写入时缺乏注意力而导致死锁码。
以下是我如何使用原始types的锁。
object readonly syncRootIteration = new object(); long iterationCount = 0; long iterationTimeMs = 0; public void IncrementIterationCount(long timeTook) { lock (syncRootIteration) { iterationCount++; iterationTimeMs = timeTook; } } public long GetIterationAvgTimeMs() { long result = 0; //if read without lock the result might not be accurate lock (syncRootIteration) { if (this.iterationCount > 0) { result = this.iterationTimeMs / this.iterationCount; } } return result; }
快乐线程:)