2013-07-03 48 views
2

當多線程我知道我需要鎖定變量,如添加和項目列表等,或者我會得到一個交叉線程異常。但是,當我分配變量時,是否需要鎖定它們?我不介意如果一個線程獲得變量的舊實例 - 我只是不希望它的錯誤。這裏是我的意思的例子:簡單分配變量時是否需要使用鎖定?

public void Run() 
    { 
     var thread1 = new Thread(new ThreadStart(Test)); 
     var thread2 = new Thread(new ThreadStart(Test)); 
     thread1.Start(); 
     thread2.Start(); 
    } 

    private static int _test; 

    private void Test() 
    { 
     while (true) 
     { 
      _test += 1; 
     } 
    } 
+0

你是什麼意思的「錯誤」? – user7116

+5

'+ ='是一個* read *和* assignment *(非原子一起)。 – Paul

+0

對不起 - 我的意思是閱讀和分配。是的,這正是我想用+ = – JoeS

回答

0

運行代碼應該給你你的答案...而不是while(true)for(i=1;1<1e6;i++),結果寫到屏幕,並運行它。

你會發現它不會加起來到2e6,而是在1.2e6左右。所以是的,你需要鎖定,如果你想出去2e6。

不要只是假設,之後總是測試和斷言。

+1

問題是,你可以做這樣的測試並讓它們工作,然後用較慢(或者不同)的硬件在另一臺機器上運行它們,並讓它失敗。 – Servy

+0

這是事實(我從經驗中知道),但這種情況很簡單,即使在簡單的體系結構中也能證明結果。 – gjvdkamp

0

你必須記住線程也可以尋找過時副本,通過鎖定你保證你是在尋找變量的版本被刷新

當我第一次開始編碼並認爲可能不需要最新的變量副本時,我會陷入無限循環,因爲我認爲變量最終會被更新,但是如果變量被緩存了,那麼它將永遠不會更新

我包含了一些簡單描述的例子,不用擔心線程的啓動方式d,當你開始循環有一個機會,你會繼續得到變量的過時副本不相關

private static bool _continueLoop = true; 
private static readonly object _continueLoopLock = new object(); 

private static void StopLoop() 
{ 
    lock(_continueLoopLock) 
     _continueLoop = false; 
} 

private static void ThreadALoopWillGetStales() 
{ 
    while(_continueLoop) 
    { 
     //do stuff 
     //this is not guaranteed to end 
    } 
} 

private static void ThreadALoopEventuallyCorrect() 
{ 
    while(true) 
    { 
     bool doContinue; 

     lock(_continueLoopLock) 
      doContinue = _continueLoop; 

     if(!doContinue) 
      break; 

     //do stuff 
     //this will sometimes result in a stale value 
     //but will eventually be correct 
    } 
} 

private static void ThreadALoopAlwaysCorrect() 
{ 
    while(true) 
    { 
     bool doContinue; 

     lock(_continueLoopLock) 
      if(!_continueLoop) 
      break; 

     //do stuff 
     //this will always be correct 
    } 
} 

private static void ThreadALoopPossibleDeadlocked() 
{ 
    lock(_continueLoopLock) 
     while(_continueLoop) 
     { 
      //if you only modify "_continueLoop" 
      //after Acquiring "_continueLoopLock" 
      //this will cause a deadlock 
     } 
} 

private static void StartThreadALoop() 
{ 
    ThreadPool.QueueUserWorkItem ((o)=>{ThreadALoopWillGetStales();}); 
} 
private static void StartEndTheLoop() 
{ 
    ThreadPool.QueueUserWorkItem((o)=> 
    { 
     //do stuff 
     StopLoop(); 
    }); 
} 

public static void Main(string[] args) 
{ 
    StartThreadALoop(); 
    StartEndTheLoop(); 
} 

,這就是爲什麼你訪問時需要某種形式的同步跨越多個線程

+0

即使你同步你仍然可以得到一個陳舊的副本,例如你需要聲明'_continueLoop' ['volatile'](http://msdn.microsoft.com/en-us/library/x13ttww7%28v= vs.71%29.aspx)(事實上,在你的例子中,鎖是不明智的,因爲布爾的賦值操作是原子的。) –

+0

@ScottChamberlain你不需要在所有情況下都這樣做 – konkked

+0

是的,但在你的情況下,鎖定不會提供任何好處,並且不會獲得您聲稱的「新鮮度」。 –

5

如果你只是分配int,那麼沒有。但在這裏你不只是分配。你正在增加。所以你需要某種同步。

在要增加,使用Interlocked.Increment

Interlocked.Increment(ref _test); 
+0

這是否也創建一個內存圍欄或一些東西,以保證'_test'的值不陳舊?文件說它是原子的,但我不認爲原子意味着同步。 (我對CLR的內存模型不是很熟悉。) – DaoWen

+0

@DaoWen是的,它會增加適當的內存障礙。原子意味着,在任何時刻你只能觀察到行動發生了,或者沒有發生。非原子操作意味着可以觀察到一些部分完成的狀態,在這種狀態下它已經開始做某些事情但還沒有完成。實現原子性的一種方式(但不是唯一的方法)是通過同步。 – Servy

+0

@Servy - 我的意思是「我不認爲原子暗示同步」是,你可以原子地將_increment_這樣的操作應用到當前線程緩存的陳舊值。我想提供一個圖書館例程,但做了這樣的事情沒有多大意義。 – DaoWen

相關問題