C#不允许在null值上进行锁定。我想我可以在锁定之前检查该值是否为null,但是因为我没有对它进行锁定,另一个线程可能会将该值变为null!我该如何避免这种竞争条件呢?
C#不允许在null值上进行锁定。我想我可以在锁定之前检查该值是否为null,但是因为我没有对它进行锁定,另一个线程可能会将该值变为null!我该如何避免这种竞争条件呢?
锁定一个永远不会为null的值,例如:
Object _lockOnMe = new Object();
Object _iMightBeNull;
public void DoSomeKungFu() {
if (_iMightBeNull == null) {
lock (_lockOnMe) {
if (_iMightBeNull == null) {
_iMightBeNull = ... whatever ...;
}
}
}
}
同时要小心避免使用双重检查锁定时出现的有趣竞态条件:双重检查锁定中的内存模型保证
_iMightBeNull
需要声明为 volatile。或者更好的做法是,使用 Lazy<T>
进行延迟初始化。 - Douglas_iMightBeNull
是否为 null,然后才对其进行锁定。您需要了解内存模型才能理解为什么这是错误的。如果想快速确认,请参见 http://csharpindepth.com/Articles/General/Singleton.aspx#dcl - Douglas_iMightBeNull
以后可能被设置为 null 时,不稳定性才是一个问题。最坏的情况是第一个 null 检查错误地将 _iMightBeNull
识别为空,并在 _lockOnMe
上锁定。如果我没记错的话,锁提供了完整的内存屏障,包括刷新,这意味着在锁内修改 _iMightBeNull
的任何人都会在退出时刷新它,并且随后进入锁的任何人都将读取(现在已更新的)值。 - Chris Shain这里有两个问题:
首先,不要对一个null
对象进行锁定。这是没有意义的,因为两个都是 null
的对象是无法区分的。
其次,在多线程环境中安全地初始化变量,可以使用双重检查锁定模式:
if (o == null) {
lock (lockObj) {
if (o == null) {
o = new Object();
}
}
}
你的问题的第一部分已经得到了回答,但我想为你的问题的第二部分添加一些内容。
在这种情况下,使用不同的对象来执行锁定更为简单。这也解决了在临界区中维护多个共享对象状态的问题,例如雇员列表和雇员照片列表。
此外,当您必须在原始类型(如int或decimal等)上获取锁时,此技术也非常有用。
我认为,如果您像其他人建议的那样使用此技术,则不需要两次执行空值检查。例如,在接受的答案中,Cris使用了两次if条件语句,这确实没有任何区别,因为锁定的对象与实际修改的对象不同,如果您锁定了一个不同的对象,则执行第一次空值检查是无用而浪费cpu的。
我建议使用以下代码:
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);
}
}
}
如果有人发现了竞争条件,请在评论中回复。从上面的代码可以看出,它解决了三个问题:首先,在锁定之前不需要进行空值检查;其次,它创建了一个关键部分而不锁定两个共享源;第三,锁定多个对象会因编写代码时缺乏注意而导致死锁。
以下是我如何使用原始类型的锁。
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;
}
多线程愉快 :)