34

我正在使用下面的代码

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));
});

谢谢。

4

6 回答 6

34

不!这根本不安全,因为不安全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));
});

但在多线程方面没有任何意义。lock在每次迭代时强制执行完全顺序执行,一堆线程将等待单个线程。

于 2011-02-16T18:25:35.400 回答
8

利用:

var processed = new ConcurrentBag<Guid>();

请参阅并行 foreach 循环 - 奇怪的行为

于 2011-02-16T18:27:31.733 回答
4

来自 Jon Skeet 的书C# in Depth

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

这些包括:

  • IProducerConsumerCollection<T>
  • BlockingCollection<T>
  • ConcurrentBag<T>
  • ConcurrentQueue<T>
  • ConcurrentStack<T>
  • ConcurrentDictionary<TKey, TValue>
  • 和别的
于 2011-02-16T18:27:14.070 回答
1

作为安德烈答案的替代方案:

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

你也可以写

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

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

于 2012-08-07T15:43:35.087 回答
1

使用 Something 类型的 ConcurrentBag

var bag = new ConcurrentBag<List<Something>>;
var items = GetAllItemsINeed();
Parallel.For(items,i =>                          
   {
      bag.Add(i.DoSomethingInEachI());
   });
于 2016-01-24T17:07:26.430 回答
0

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

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

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

于 2011-02-16T18:39:35.183 回答