2011-07-29 86 views
9

似乎有很多關於多線程編程的知識,它有點嚇人。使整個方法線程安全的最簡單方法?

對於我目前的需要,我只是想防止的方法從另一個線程再次被稱爲完成之前,我的問題是:

這是適當的(安全)的方法,使方法線程安全?

class Foo 
{ 
    bool doingWork; 
    void DoWork() 
    { 
     if (doingWork) // <- sophistocated thread-safety 
      return;  // <- 

     doingWork = true; 

     try 
     { 
      [do work here] 
     } 
     finally 
     { 
      doingWork = false; 
     } 
    } 
} 

如果這還不夠,什麼是實現這一目標的最簡單的方法?


編輯:關於方案更多信息:

  • 有富的只有一個實例

  • Foo.DoWork()將被從線程池線程呼籲Elapsed System.Timers.Timer的事件。

  • 通常Foo.DoWork()將在下一次的 稱爲前完成劫,但我要爲渺茫的機會,它會長期來看, 並獲得完成之前再次調用代碼。


(我還沒有聰明到可以肯定,如果這個問題能夠標記語言無關,所以我沒有。開明的讀者,可隨時做,如果適用)。

+0

是爲每個線程實例化一個Foo類型的對象,還是它跨多個線程共享? – NotMe

+0

提供有關調用dowork方法的代碼的更多細節,是否有多個線程? – sll

+0

要麼支持重入,要麼設計你的代碼,這樣它就不會發生。只要在事件發生時進行救助就不太可能是正確的解決方案。但是你的編輯清楚地表明線程安全實際上是問題,而不是重新進入。 –

回答

8

您的代碼不是線程安全的。您應該使用lock關鍵字。

在你當前的代碼:

if (doingWork) 
     return; 

    // A thread having entered the function was suspended here by the scheduler. 

    doingWork = true; 

當一個線程來通過,它也將進入該功能。

這就是爲什麼應該使用lock結構。它基本上與您的代碼,但沒有一個線程的風險在中間被打斷:

class Foo 
{ 
    object lockObject = new object; 
    void DoWork() 
    { 
     lock(lockObject) 
     { 
      [do work here] 
     } 
    } 
} 

注意,該代碼比原來的略有不同的語義。這段代碼將導致第二個線程進入等待狀態,然後完成工作。您的原始代碼使第二個線程中止。爲了更接近您的原始代碼,不能使用C#lock語句。底層Monitor結構具有直接使用:

class Foo 
{ 
    object lockObject = new object; 
    void DoWork() 
    { 
     if(Monitor.TryEnter(lockObject)) 
     { 
      try 
      { 
       [do work here] 
      } 
      finally 
      { 
       Monitor.Exit(lockObject); 
      } 
     } 
    } 
} 
+0

我並不需要放棄,所以鎖定方法應該沒問題。甜 - 這很簡單!謝謝。 –

+0

[瓦倫丁](http://stackoverflow.com/questions/6879468/simplest-way-to-make-a-whole-method-thread-safe/6879698#6879698)有一種方法,只是鎖中止( )。對你的評論感興趣。 –

0

http://msdn.microsoft.com/en-us/library/system.threading.barrier.aspx

可能想使用屏障來代替它,它會爲您完成所有工作。這是控制重入代碼的標準方式。還允許您一次控制執行該工作的線程數量(如果允許超過1個)。

+0

這似乎過於複雜,我需要。我真的只想實現我的代碼片段暗示的內容:重新進入時跳出。 –

+0

檢查障礙中的參與者數量,如果它> 0返回,則在最後刪除參與者時添加參與者並開始工作。屏障是爲了線程安全。 –

+0

你可以做檢查並以原子方式添加一個參數嗎?如果是這樣,怎麼樣? –

3

重新進入與多線程無關。

重入方法是一種方法,最終可以在同一個線程中從本身內部調用。
例如,如果方法引發事件,並且處理該事件的客戶端代碼在事件處理程序內再次調用該方法,則該方法是可重入的。
保護該方法免於再次入侵意味着確保如果從內部調用該方法,它將不會發出異常或拋出異常。

只要所有內容都在同一個線程中,您的代碼就不會在同一個對象實例中重新進入。

除非[do work here]能夠運行外部代碼(例如,通過引發一個事件,或者通過調用其他代理或方法),它並不是重新進入的。

您編輯的問題表明整個部分與您無關。
無論如何你應該可以閱讀它。


你可能是(編輯:是)尋找排他性 –確保如果同時調用多個線程的方法將不會運行兩次一次。
您的代碼不是唯一的。如果兩個線程一次運行該方法,並且它們都一次運行if語句,則它們都會通過if,然後都設置doingWork標誌,並且都將運行整個方法。

爲此,請使用lock關鍵字。

+0

那麼它確實說標籤中多線程所以人們只能假設。 –

+1

人們只能假設_what_?這個問題與多線程無關。 **編輯**:現在,它確實。 – SLaks

+0

不可否認,我只是在考慮從另一個線程再次調用該方法。謝謝你指出 - 我已經編輯了這個問題。不過,我相信這個術語也可以應用於多線程場景。如果不是的話,當一個方法需要能夠應對被另一個線程調用的時候,你會怎樣調用它? –

2

如果你想容易代碼和不約的表現太在意它可以那麼容易,因爲

class Foo 
{ 
    bool doingWork; 
object m_lock=new object(); 
    void DoWork() 
    { 
     lock(m_lock) // <- not sophistocated multithread protection 
{ 
     if (doingWork) 
      return;  
     doingWork = true; 
} 


     try 
     { 
      [do work here] 
     } 
     finally 
     { 
lock(m_lock) //<- not sophistocated multithread protection 
{ 
      doingWork = false; 
} 
     } 
    } 

}

如果你想封閉一點鎖定,你可以創建一個像這樣線程安全的屬性:

public bool DoingWork 
{ 
get{ lock(m_Lock){ return doingWork;}} 
set{lock(m_lock){doingWork=value;}} 
} 

現在你可以用它來代替字段,但是它會導致更多的時間用於鎖定原因使用次數增加。

或者你可以使用全柵欄的方法(從大螺紋書Joseph Albahari online threading

class Foo 
{ 
    int _answer; 
    bool _complete; 

    void A() 
    { 
    _answer = 123; 
    Thread.MemoryBarrier(); // Barrier 1 
    _complete = true; 
    Thread.MemoryBarrier(); // Barrier 2 
    } 

    void B() 
    { 
    Thread.MemoryBarrier(); // Barrier 3 
    if (_complete) 
    { 
     Thread.MemoryBarrier();  // Barrier 4 
     Console.WriteLine (_answer); 
    } 
    } 
} 

他指出,全圍欄2X比lock語句快。在某些情況下,您可以通過刪除對MemoryBarrier()的不必要的調用來提高性能,但使用lock很簡單,更清晰且不易出錯。

我相信這也可以使用基於INT的Interlocked類做基於int的工作領域。

+0

你的lock()(鎖定對doingWork的訪問)和Anders的lock()方法(鎖定對這個工作的代碼的訪問)有什麼區別? –

+0

有邏輯上的區別。正如我從他的回答方法中看到的那樣是可重入的,並且進一步的呼叫正在排隊。在我的代碼中,我假設你不想讓其他線程排隊這個方法,如果它在一個線程上運行的話。根據我的經驗,我的方法通常是首選,例如,當您不想允許用戶通過雙擊按鈕啓動多個服務器請求時。 PS:他說那不能用鎖來完成,我用鎖來做。 Monitor.TryEnter可以獲得更好的性能,但是對於日常需求,我想可以稍微高級一些,因爲id會將其留給關鍵的性能代碼。 –

+0

哦,我看,很酷。你怎麼樣,安德斯? :) –