2011-07-20 88 views
3

我讀了一本關於並行編程的它說,它不是線程保存元素添加到列表中,而無需使用鎖的結果將是不可預知的。例如,如果我們必須添加80萬個元素到列表中,最終結果將少於80萬個元素。C#與列表並行編程 - 正在讀線程安全?

現在我想知道是否線程保存到從列表中讀取元素。例如可以說,我有一個列表BlackListedNumbers

List<int> BlackListedNumbers = new List<int> {10, 50 ....... n}; 
//lets say there is 500 000 elements in the list 

,並含有10 000 000號另一個列表Numbers,很明顯,我將使用parallel.Foreach來完成這個任務,我要的是含所有數字Final列表Numbers不在BlackListedNumbers列表

List<int> finalList = new List<int>(); 
Parallel.ForEach(Numbrs, 
    num => 
    { 
     if (!blackListedNumbrs.Contains(num)) 
     { 
     lock (finalList) 
     { 
      finalList.Add(num); 
     } 
     } 
    }); 

我知道這是不是得到這個工作最有效的方式,但我只是想說明問題。

所以我的問題是:是否有跟帖保存到閱讀列表blackListedNumbrs結果,我會得到100%準確的結果?

+0

對於列表,那就是安全的。然而,從多線程的數據結構中讀取數據通常是不安全的,即使是所謂的「不可變」結構。該集合仍然必須設計爲支持安全的多線程。請參閱http://blogs.msdn。com/b/ericlippert/archive/2011/05/23/read-only-and-threadsafe-are-different.aspx瞭解更多想法。 –

回答

12

MSDN

一個List<T>可以支持多個讀者同時,只要收集不被修改。

所以,如果你從來沒有修改列表,你應該罰款。

請注意,使用HashSet<int>會更有效 - 而且HashSet<T>也支持多個閱讀器。您可以使用並行LINQ,使您的查詢更甜,而且幾乎肯定更高效:

// If you want duplicates in Numbers to still come up as duplicates in the result 
HashSet<int> blacklistedSet = new HashSet<int>(blackListedNumbers); 
List<int> finalList = Numbers.AsParallel() 
          .Where(x => !blacklistedSet.Contains(x)) 
          .ToList(); 

// Or if you just want a set-based operation: 

List<int> finalList = Numbers.AsParallel() 
          .Except(blacklistedSet) 
          .ToList(); 

的效果好很多,而且無需鎖定:)


正如在評論中指出,我沒有任何文件來支持。但是,從一組讀書不需要修改任何共享狀態,所以它至少讓 ...

+1

@Jon,我的理解是,使用索引器('list [i]')同時從列表中讀取是安全的,但循環('foreach')將不安全,因爲它使用枚舉器。這是正確的還是它是安全的從多個線程列表(當然假設它從未被寫入)? –

+1

@Darin:如果沒有任何內容正在修改列表,我相信它是完全正確的迭代它。它不像列表迭代器需要做任何修改。 MSDN文檔說它是不安全的,但是如果你閱讀它,就會談到在其他寫入時發生的情況。請記住,每次調用GetEnumerator()都會返回一個*獨立的*迭代器。 –

+7

@Darin:使用兩個枚舉器(每個線程上一個)從一個列表中讀取是安全的。使用一個枚舉器從一個列表讀取數據並不安全,並且在不同線程上調用枚舉器上的MoveNext。 * list *對多個閱讀器是線程安全的,但是將枚舉器向前移動*寫入枚舉器*,這是不安全的。 –

1

只要沒有人其他人在寫/添加/刪除blackListedNumbers

0

閱讀是安全的所以你上面的代碼應該可以正常工作,並且確實將記錄添加到具有多個線程的列表中會導致問題。 .NET 4.0引入Thread-Safe Colections在你的情況下,你可以使用ConcurrentBag來使用多個線程向項目添加項目。

這是我使用它的例子:

var data = new ConcurrentBag<DJVSStatsEv>(); 

Parallel.ForEach(globalData.ValuationEventsPit, item => 
       { 
        data.Add(new DJVSStatsEv(item.DateYearMonth, item.EventType, eventGroup) {PostVal = item.PostVal, PreVal = item.PreVal, Raised = item.Raised}); 
      });