2012-03-01 25 views
7

我试图编写一个线程安全的方法,它只能被调用一次(每个对象实例)。如果之前被调用过,应抛出异常。如何:编写一个只能调用一次的线程安全方法?

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

  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; 
    

    如果我没有记错的话,这个方案具有无锁的优势(这在我看来似乎并不相关),并且它需要更少的私人领域。

我也有理由认为哪个解决方案应该是首选的,如果还有更好的方法可以提出进一步的建议。

回答

5

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

+0

出于好奇:当你说它“不那么复杂”时,你似乎是指盲目背后发生的一切;你如何判断一个普通程序员的可读性/易于理解'Interlocked.ExchangeCompare'结构? – stakx 2012-03-01 10:51:57

+1

@stakx:这就是评论。当一个程序员遇到他们不明白的东西时,他们应该查找它,以便他们理解它。这就是他们如何成为更好的程序员。 – thecoop 2012-03-01 10:53:23

+0

@thecoop我不同意,是的这是最正确的解决方案,但它并不简单,您需要了解原子操作等。这是某种初始化过程,我建议遵循广泛使用的初始化模式(例如double检查锁)。此外,这些模式可以防止您错过执行线程时很容易发生的事情。 – ntziolis 2012-03-01 10:55:29

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 
    } 
} 

通常,当接触到这样的尝试的问题,并按照众所周知像一个patters以上。
这使得它更容易识别,而且更不容易出错,因为当你遵循一个模式时,你不太可能错过某些东西,特别是当涉及到线程时。
在你的情况下,第一个如果没有多大意义,但通常你会想要执行实际的逻辑,然后设置标志。当你执行你的代码(可能代价很高)时,第二个线程会被阻塞。

关于第二个示例:
是的,这是正确的,但不要使它比它更复杂。你应该有很好的理由不使用简单的锁定,在这种情况下,它会使代码更加复杂(因为Interlocked.CompareExchange()不太清楚)而没有实现任何东西(正如你指出的那样锁定更少锁定来设置布尔标志并不是真的在这种情况下的好处)。

+0

** 1。**这似乎使读/写'someFlag'非原子。你确定这是正确的吗? ** 2。**基于Interlocked.CompareExchange的解决方案如何? – stakx 2012-03-01 10:32:21

+0

sry忘了最重要的一行 – ntziolis 2012-03-01 10:35:11

+0

嗯......如果你打算抛出一个异常,是否会锁定这样的问题呢? – stakx 2012-03-01 10:39:17

-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 
    }  
+1

对不起,这不能回答这个问题。 (顺便说一下,我知道任务并行库,但它不能在任何地方使用,例如,在那里的方法实现接口方法。) – stakx 2012-03-01 10:38:00

+1

根据你的情况修复 – pamidur 2012-03-01 10:42:42

+0

@pamidur你正试图解决不同的问题 – ntziolis 2012-03-01 10:57:40

相关问题