使用 Parallel.ForEach() 的 yield return 的线程安全

Posted

技术标签:

【中文标题】使用 Parallel.ForEach() 的 yield return 的线程安全【英文标题】:Thread safety of yield return with Parallel.ForEach() 【发布时间】:2013-06-05 23:38:01 【问题描述】:

考虑以下代码示例,它创建一个可枚举的整数集合并并行处理:

using System.Collections.Generic;
using System.Threading.Tasks;

public class Program

    public static void Main()
    
        Parallel.ForEach(CreateItems(100), item => ProcessItem(item));
    

    private static IEnumerable<int> CreateItems(int count)
    
        for (int i = 0; i < count; i++)
        
            yield return i;
        
    

    private static void ProcessItem(int item)
    
        // Do something
    

是否保证Parallel.ForEach() 生成的工作线程每个都获得不同的项目,或者是否需要一些围绕i 递增和返回的锁定机制?

【问题讨论】:

只使用 Enumerable.Range 。 @newStackExchangeInstance:我认为,这只是一个迭代器示例。 @newStackExchangeInstance:Enumerable.Range() 如何帮助我并行处理IEnumerable 顺便说一句,在这种特定情况下,使用Parallel.For() 可能更有意义。 【参考方案1】:

Parallel.ForEach&lt;TSource&gt;,当TSourceIEnumerable&lt;T&gt; 时,会为IEnumerable&lt;T&gt; 创建一个分区器,其中包含自己的内部锁定机制,因此您不需要实现任何线程-迭代器中的安全性。

每当工作线程请求一大块项目时,分区器将创建一个内部枚举器,它:

    获取共享锁 遍历源(从它离开的地方)以检索项目块,将项目保存在私有数组中 释放锁,以便可以满足其他块请求。 从其私有数组中为工作线程提供服务。

如您所见,出于分区目的通过IEnumerable&lt;T&gt; 运行是顺序的(通过共享锁访问),并且分区是并行处理的。

【讨论】:

通过 google 找到了这个,做了它,然后 VS 告诉它可以简化为 Parallel.ForEach(...)。不管被告知特定类型,ForEach 现在是否有可能拥有此功能? @MarlonRegenhardt 是的,VS 是正确的。该简化功能称为“类型推断”。例如,当您键入Parallel.ForEach(myList, ...) 时,c# 编译器可以通过查看myList 的泛型类型来“推断”泛型类型参数(&lt;TSource&gt;)。【参考方案2】:

TPL 和 PLINQ 使用 partitioners 的概念。

Partitioner 是一种继承Partitioner&lt;TSource&gt; 的类型,用于将源序列拆分为多个部分(或分区)。内置分区器旨在将源序列拆分为不重叠的分区。

【讨论】:

我认为这实际上并不能回答问题:问题中的代码(从表面上看,根本不使用Partitioner)是线程安全的吗? @svick:问题是“是否保证由 Parallel.ForEach() 生成的工作线程每个都获得不同的项目”。问题不在于迭代器的线程安全。

以上是关于使用 Parallel.ForEach() 的 yield return 的线程安全的主要内容,如果未能解决你的问题,请参考以下文章

何时使用 Parallel.ForEach,何时使用 PLINQ

何时使用 Parallel.ForEach,何时使用 PLINQ

使用 Parallel.ForEach 之外的选项

Parallel.ForEach 内存不足异常

Parallel.ForEach 中的不一致 [关闭]

Parallel.ForEach 使用多线遍历循环