2011-07-20 36 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}); 
      });