列表线程安全

发布于 2024-10-17 14:50:04 字数 413 浏览 3 评论 0原文

我正在使用下面的代码

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

谢谢。

I am using the below code

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

Is the above code thread safe? Is there a chance of processed list getting corrupted? Or should i use a lock before adding?

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

thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

花海 2024-10-24 14:50:04

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

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

No! It is not safe at all, because processed.Add is not. You can do following:

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

Keep in mind that Parallel.ForEach was created mostly for imperative operations for each element of sequence. What you do is map: project each value of sequence. That is what Select was created for. AsParallel scales it across threads in most efficient manner.

This code works correctly:

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

but makes no sense in terms of multithreading. locking at each iteration forces totally sequential execution, bunch of threads will be waiting for single thread.

野侃 2024-10-24 14:50:04

使用:

var processed = new ConcurrentBag<Guid>();

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

Use:

var processed = new ConcurrentBag<Guid>();

See parallel foreach loop - odd behavior.

风情万种。 2024-10-24 14:50:04

来自 Jon Skeet 的书 C# 深入了解

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

其中包括:

  • IProducerConsumerCollection
  • BlockingCollection
  • ConcurrentBag
  • ConcurrentQueue
  • ConcurrentStack
  • ConcurrentDictionary

From Jon Skeet's Book C# in Depth:

As part of Parallel Extensions in .Net 4, there are several new collections in a new System.Collections.Concurrent namespace. These are designed to be safe in the face of concurrent operations from multiple threads, with relatively little locking.

These include:

  • IProducerConsumerCollection<T>
  • BlockingCollection<T>
  • ConcurrentBag<T>
  • ConcurrentQueue<T>
  • ConcurrentStack<T>
  • ConcurrentDictionary<TKey, TValue>
  • and others
梦旅人picnic 2024-10-24 14:50:04

作为 Andrey 的 answer 的替代方案:

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

您也可以写

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

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

As alternative to the answer of Andrey:

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

You could also write

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

This makes the query that is behind it even more efficient because no merge is required, MSDN.
Make sure the SomeProcessingFunc function is thread-safe.
And I think, but didn't test it, that you still need a lock if the list can be modified in an other thread (adding or removing) elements.

混浊又暗下来 2024-10-24 14:50:04

尽管有上述所有可能的更有效的建议,您仍然可以改进您的工作代码:

varprocessed=newList();
Parallel.ForEach(items, item =>; 
{
    锁(项目.SyncRoot)
        已处理。添加(SomeProcessingFunc(项目));
});

...通过仅在将项目添加到您的收藏时防止竞争条件,从而通过以下简单的更改限制您获得对共享资源的独占访问权的时间:

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

Despite all of the above, possibly, more effective suggestions, you could still improve your working code:

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

...by only preventing racing conditions when adding items to your collection, thus limiting the time you get exclusive access to the shared resource with this simple change:

var processed = new List<Guid>();
Parallel.ForEach(items, item => 
{
    var result = SomeProcessingFunc(item);
    
    lock(items.SyncRoot)
        processed.Add(result);
});
新人笑 2024-10-24 14:50:04

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

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

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

reading is thread safe, but adding is not. You need a reader/writer lock setup as adding may cause the internal array to resize which would mess up a concurrent read.

If you can guarantee the array won't resize on add, you may be safe to add while reading, but don't quote me on that.

But really, a list is just an interface to an array.

只为守护你 2024-10-24 14:50:04

使用 Something 类型的 ConcurrentBag

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

Using ConcurrentBag of type Something

var bag = new ConcurrentBag<List<Something>>;
var items = GetAllItemsINeed();
Parallel.For(items,i =>                          
   {
      bag.Add(i.DoSomethingInEachI());
   });
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文