9

我正在尝试编写一个线程安全的方法,它只能被调用一次(每个对象实例)。如果之前已经调用过,应该抛出异常。

我想出了两个解决方案。他们都正确吗?如果不是,他们有什么问题?

  1. lock

    public void Foo()
    {
        lock (fooLock)
        {
            if (fooCalled) throw new InvalidOperationException();
            fooCalled = true;
        }
        …
    }
    private object fooLock = new object();
    private bool fooCalled;
    
  2. Interlocked.CompareExchange

    public void Foo()
    {
        if (Interlocked.CompareExchange(ref fooCalled, 1, 0) == 1)
            throw new InvalidOperationException();
        …
    }
    private int fooCalled;
    

    如果我没记错的话,这个解决方案的优点是无锁(这在我的情况下似乎无关紧要),并且它需要更少的私有字段。

我也愿意接受合理的意见,应该首选哪种解决方案,如果有更好的方法,我也会提出进一步的建议。

4

3 回答 3

7

您的Interlocked.CompareExchange解决方案看起来最好,并且(如您所说)是无锁的。它也比其他解决方案简单得多。锁是相当重量级的,CompareExchange但可以编译为单个 CAS cpu 指令。我说和那个一起去。

于 2012-03-01T10:39:30.920 回答
0

双重检查锁定模式是您所追求的:

这就是你所追求的:

class Foo
{
   private object someLock = new object();
   private object someFlag = false;


  void SomeMethod()
  {
    // to prevent locking on subsequent calls         
    if(someFlag)
        throw new Exception();

    // to make sure only one thread can change the contents of someFlag            
    lock(someLock)
    {
      if(someFlag)
        throw new Exception();

      someFlag = true;                      
    }

    //execute your code
  }
}

通常,当遇到此类问题时,请尝试遵循上述众所周知的模式。
这使它易于识别且不易出错,因为您在遵循模式时不太可能错过某些东西,尤其是在线程方面。
在您的情况下,第一个 if 没有多大意义,但通常您会想要执行实际逻辑然后设置标志。当您执行(可能非常昂贵)代码时,第二个线程将被阻塞。

关于第二个示例:
是的,它是正确的,但不要让它比它更复杂。您应该有很好的理由不使用简单锁定,在这种情况下,它会使代码更加复杂(因为Interlocked.CompareExchange()鲜为人知)而没有实现任何目标(正如您所指出的那样,减少锁定以防止锁定以设置布尔标志并不是真正的在这种情况下受益)。

于 2012-03-01T10:29:45.837 回答
-2
    Task task = new Task((Action)(() => { Console.WriteLine("Called!"); }));
    public void Foo()
    {
        task.Start();
    }

    public void Bar()
    {
        Foo();
        Foo();//this line will throws different exceptions depends on 
              //whether task in progress or task has already been completed
    }    
于 2012-03-01T10:30:35.400 回答