在运行代码之前,可以在锁之前和之内"仔细检查"吗?

Iai*_*ser 10 c# multithreading thread-safety

在使用线程安全时,我发现自己总是在执行锁定块中的代码之前"仔细检查",我想知道我是否正在做正确的事情.考虑以下三种做同样事情的方法:

例1:

private static SomeCollection MyCollection;
private static Object locker;
private void DoSomething(string key)
{
    if(MyCollection[key] == null)
    {
         lock(locker)
         {
              MyCollection[key] = DoSomethingExpensive(); 
         }
    }
    DoSomethingWithResult(MyCollection[key]);
}
Run Code Online (Sandbox Code Playgroud)

例2:

private static SomeCollection MyCollection;
private static Object locker;
private void DoSomething(string key)
{
    lock(locker)
    {
         if(MyCollection[key] == null)
         {
              MyCollection[key] = DoSomethingExpensive(); 
         }
    }
    DoSomethingWithResult(MyCollection[key]);
}
Run Code Online (Sandbox Code Playgroud)

例3:

private static SomeCollection MyCollection;
private static Object locker;
private void DoSomething(string key)
{
    if(MyCollection[key] == null)
    {
        lock(locker)
        {
             if(MyCollection[key] == null)
             {
                  MyCollection[key] = DoSomethingExpensive(); 
             }
        }
    }
    DoSomethingWithResult(MyCollection[key]);
}
Run Code Online (Sandbox Code Playgroud)

我总是倾向于示例3,这就是为什么我认为我做的是正确的事情

  • 线程1进入 DoSomething(string)
  • MyCollection[key] == null 所以线程1获得一个锁,就像线程2进入一样
  • MyCollection[key] == null 仍然是,因此线程2等待获取锁定
  • 线程1计算值MyCollection[key]并将其添加到集合中
  • 线程1释放锁定并调用 DoSomethingWithResult(MyCollection[key]);
  • 线程2获得锁定,到时为止 MyCollection[key] != null
  • 线程2什么都不做,释放锁并继续它的快乐方式

示例1可行,但线程2可能存在冗余计算的风险MyCollection[key].

示例2可以工作,但是每个线程都会获得一个锁,即使它不需要 - 这可能是一个(无可否认的很小)瓶颈.如果你不需要,为什么要保留线程?

我是否在思考这个问题,如果是这样,处理这些情况的首选方式是什么?

Guf*_*ffa 6

不应使用第一种方法.正如您所意识到的那样,它会泄漏,因此不止一个线程最终会运行昂贵的方法.该方法所用的时间越长,另一个线程也会运行它的风险就越大.在大多数情况下,它只是一个性能问题,但在某些情况下,结果数据稍后会被一组新数据替换也可能是一个问题.

第二种方法是最常用的方法,如果数据被频繁访问以致锁定成为性能问题,则使用第三种方法.

  • @IainFraser:是的,如果集合不是线程安全的,则必须在访问它的*每个*代码块周围有一个锁,并且必须使用相同的对象作为标识符(示例中的“locker”对象) )对于所有这些锁。此外,标识符必须与集合具有相同的范围,即,如果集合位于静态变量中,则标识符也应该是静态的,如果集合位于实例成员中,则标识符也应该是实例成员。在您的示例中,集合是实例成员,但标识符位于静态变量中。 (2认同)