如何:编写一个只能调用一次的线程安全方法?

时间:2012-03-01 10:21:20

标签: c# .net multithreading interlocked compare-and-swap

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

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

  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;
    

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

  3. 我也愿意接受合理的意见,哪些解决方案应该是首选的,并且如果有更好的方法可以提出进一步的建议。

3 个答案:

答案 0 :(得分:6)

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

答案 1 :(得分: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()鲜为人知)而没有实现任何东西(正如你所指出的那样,对锁定设置一个布尔值的锁定更少)在这种情况下,旗帜并不是真正的好处。)

答案 2 :(得分:-1)

    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
    }