2009-10-12 32 views

回答

5

線程安全的類型可以安全地從多個線程訪問,而不必考慮併發性。這通常意味着該類型是隻讀的。

有趣的是,Queue<T>不是線程安全 - 只要隊列沒有被修改,但它可以支持併發讀取,但與線程安全不同。

爲了考慮線程安全性,考慮如果兩個線程訪問一個Queue<T>會發生什麼情況,第三個線程出現並開始添加或刪除此Queue<T>。由於此類型不限制此行爲,因此它不是線程安全的。

25

「線程安全」有點不幸,因爲它沒有真正的定義。基本上這意味着對象的某些操作在通過多線程操作對象時保證合理運行。

考慮最簡單的例子:一個計數器。假設你有兩個正在增加計數器的線程。如果事件順序如下:

  • 線程1從計數器讀取,得到 爲零。
  • 線程從計數器讀取兩次,得到 爲零。
  • 線程一遞增零,寫入 一個來反擊。
  • 線程兩個增量零,寫入 一個來反擊。

然後注意計數器是如何「失去」其中一個增量的。計數器上的簡單增量操作不是線程安全的;讓它們成爲線程安全的,你可以使用鎖或InterlockedIncrement。

與隊列類似。非線程安全隊列可能會「失去」排隊的方式與非線程安全計數器可能會丟失增量的方式相同。更糟糕的是,如果您在多線程場景中不正確地使用它們,線程安全隊列甚至可能會崩潰或產生瘋狂的結果。

「線程安全」的難點在於它沒有明確定義。它是否意味着「不會崩潰」?這是否意味着會產生明智的結果?例如,假設你有一個「線程安全」的集合。這段代碼是否正確?

if (!collection.IsEmpty) Console.WriteLine(collection[0]); 

不,即使集合是「線程安全的」,這並不意味着這段代碼是正確的;另一個線程可能會在收集之後將收集留空,但在寫入線之前,因此此代碼可能會崩潰,即使該對象被稱爲「線程安全」。實際上確定操作的每個相關組合都是線程安全的,這是一個極其困難的問題。

現在來談談你的實際情況:任何告訴你「你應該使用Queue類,更好,因爲它是線程安全的」的人可能並不清楚他們在說什麼。首先,隊列不是線程安全的。其次,如果僅在單個線程上使用該對象,則隊列是否爲線程安全是完全不相關的!如果您有一個將在多個線程中訪問的集合,那麼,正如我在上面的示例中所指出的那樣,無論集合本身是否是「線程安全」,都有一個極其困難的問題需要解決。您必須確定您在集合上執行的每個操作組合都是線程安全的。這是一個非常困難的問題,如果它是你所面對的問題,那麼你應該使用專家對這個難題的服務。

+5

+1對於堅實的專家證詞。你也指出「線程安全」是一個相當模糊的概念。 – 2009-10-12 04:13:57

+1

我喜歡每個線程安全相關的問題都由同一位老兄回答,我現在已經閱讀了大約2個小時。 – 2015-11-24 09:36:32

1

在處理多線程時,您通常必須處理併發問題。術語「併發問題」是指由兩個不同的執行上下文中的指令交叉存儲在兩個資源共享的資源上而特別引入的問題。這裏,就線程安全而言,執行上下文是一個進程中的兩個線程;然而,在相關主題中,他們可能是流程。

線程安全措施的實施主要是爲了實現兩個目標。首先是重新確定關於如果線程上下文切換(否則由操作系統控制,因此在用戶級程序中基本上不確定),防止某些任務留下半完成或兩個上下文寫入一個接一個地在內存中的相同位置。大多數度量僅僅使用一點硬件支持的指令等,以及軟件級別synchronization constructs來強制所有其他執行上下文遠離數據類型,而另一個執行上下文正在做不應該中斷的工作。

通常,只讀對象是線程安全的。如果對象沒有在中間修改,那麼許多不是隻讀的對象都可以在多線程中發生數據訪問(只讀),而不會出現問題。但這不是線程安全。線程安全性是指將所有方式的數據類型都完成爲數據類型,以防止一個線程對其進行任何修改,即使在處理多個併發讀寫時也會導致數據損壞或死鎖。