【问题标题】:List<T> thread safetyList<T> 线程安全
【发布时间】:2011-02-16 18:22:28
【问题描述】:

我正在使用下面的代码

var processed = new List<Guid>();
Parallel.ForEach(items, item => 
{
    processed.Add(SomeProcessingFunc(item));
});

上面的代码线程安全吗?处理列表是否有可能损坏?或者我应该在添加之前使用锁吗?

var processed = new List<Guid>();
Parallel.ForEach(items, item => 
{
    lock(items.SyncRoot)
        processed.Add(SomeProcessingFunc(item));
});

谢谢。

【问题讨论】:

  • 你看过 MSDN 吗?这里:msdn.microsoft.com/en-us/library/…
  • @Martinho:是的。我读到 List 不是线程安全的。但我无法理解,即使将多个线程添加到列表中,这如何破坏列表。
  • @stackoverflowuser:一个例子:列表跟踪它有多少元素。当您添加一个时,它会将其放在下一个位置,并增加计数。好吧,这里有一个竞争条件:两个线程都可以添加一个元素,并且计数只增加一个(并且一个元素在这个过程中丢失了)。
  • @mellmokb:感谢您的链接。这很有帮助。

标签: c# list c#-4.0 task-parallel-library parallel-extensions


【解决方案1】:

不!这根本不安全,因为processed.Add 不安全。您可以执行以下操作:

items.AsParallel().Select(item => SomeProcessingFunc(item)).ToList();

请记住,Parallel.ForEach 主要是为序列中每个元素的命令式 操作创建的。您所做的是映射:投影序列的每个值。这就是 Select 的创建目的。 AsParallel 以最有效的方式跨线程扩展它。

这段代码可以正常工作:

var processed = new List<Guid>();
Parallel.ForEach(items, item => 
{
    lock(items.SyncRoot)
        processed.Add(SomeProcessingFunc(item));
});

但在多线程方面毫无意义。 locking 在每次迭代时强制完全顺序执行,一堆线程将等待单个线程。

【讨论】:

  • 完整说明原因:请参阅msdn.microsoft.com/en-us/library/6sh2ey19.aspx 接近尾声,它有一个关于线程安全的主题
  • @rene: 将#c9721fa0-1cd9-4a21-818c-98d164c9fc14添加到该地址的末尾,它直接指向相关部分;)
  • 感谢您的回复。使用这里提到的 ConcurrentBag 会更好吗stackoverflow.com/questions/4779165/…
  • 你能请。就 AsParallel 是否比使用 ConcurrentBag 更好? 提供您的意见
  • @stackoverflowuser 是的,当你真的需要并发访问数据结构时,你应该使用ConcurrentBag&lt;T&gt;。但实际上像 PLINQ 这样的高级库很少需要它。
【解决方案2】:

用途:

var processed = new ConcurrentBag<Guid>();

parallel foreach loop - odd behavior

【讨论】:

  • ConcurrentQueue&lt;T&gt; is preferableConcurrentBag&lt;T&gt;
【解决方案3】:

来自 Jon Skeet 的书C# in Depth

作为 .Net 4 中并行扩展的一部分,在新的 System.Collections.Concurrent 命名空间中有几个新集合。这些被设计为在面对来自多个线程的并发操作时是安全的,并且锁定相对较少。

这些包括:

  • IProducerConsumerCollection&lt;T&gt;
  • BlockingCollection&lt;T&gt;
  • ConcurrentBag&lt;T&gt;
  • ConcurrentQueue&lt;T&gt;
  • ConcurrentStack&lt;T&gt;
  • ConcurrentDictionary&lt;TKey, TValue&gt;
  • 及其他

【讨论】:

  • 同意。请参阅我的答案,它使用 System.Collections.Concurrent 命名空间中的类型。
【解决方案4】:

作为 Andrey 的 answer 的替代品:

items.AsParallel().Select(item => SomeProcessingFunc(item)).ToList();

你也可以写

items.AsParallel().ForAll(item => SomeProcessingFunc(item));

这使得它后面的查询更加高效,因为不需要合并,MSDN。 确保SomeProcessingFunc 函数是线程安全的。 而且我认为,但没有测试,如果列表可以在其他线程中修改(添加或删除)元素,您仍然需要锁定。

【讨论】:

    【解决方案5】:

    使用 Something 类型的 ConcurrentBag

    var bag = new ConcurrentBag<List<Something>>;
    var items = GetAllItemsINeed();
    Parallel.For(items,i =>                          
       {
          bag.Add(i.DoSomethingInEachI());
       });
    

    【讨论】:

    • 应该是var bag = new ConcurrentBag&lt;Something&gt;();
    【解决方案6】:

    读取是线程安全的,但添加不是。您需要设置读取器/写入器锁定,因为添加可能会导致内部数组调整大小,从而导致并发读取混乱。

    如果你能保证数组在添加时不会调整大小,你可以在阅读时安全地添加,但不要引用我的话。

    但实际上,列表只是数组的接口。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-11-26
      • 2011-08-17
      • 1970-01-01
      • 1970-01-01
      • 2014-08-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多