C#Parallel.ForEach/Parallel.For分区的工作原理

C#Parallel.ForEach/Parallel.For分区的工作原理,c#,partitioning,parallel.foreach,C#,Partitioning,Parallel.foreach,我有一些关于使用分区方法的Parallel.ForEach的基本问题,我面临一些问题,因此我想了解这段代码是如何工作的以及它的流程 代码示例 与上述代码相关的问题: 他们是否在并行foreach内部进行分区工作 当我调试代码时,我可以看到代码的迭代(执行)次数超过5次,但据我所知,它应该只触发5次-Enumerable.Range(1,5) 这个代码什么时候会被触发?在Parallel.Foreach和Parallel.For中,有两个块由{}分隔。这两个块是如何执行并相互作用的 奖金

我有一些关于使用分区方法的Parallel.ForEach的基本问题,我面临一些问题,因此我想了解这段代码是如何工作的以及它的流程

代码示例
与上述代码相关的问题:

  • 他们是否在并行foreach内部进行分区工作

  • 当我调试代码时,我可以看到代码的迭代(执行)次数超过5次,但据我所知,它应该只触发5次-
    Enumerable.Range(1,5)

  • 这个代码什么时候会被触发?在
    Parallel.Foreach
    Parallel.For
    中,有两个块由
    {}
    分隔。这两个块是如何执行并相互作用的


  • 奖金问题:

    请看这段代码,其中5次迭代没有发生,而更多的迭代正在发生。当我使用Parallel For而不是foreach时。看看密码,告诉我哪里出错了

        var result = new StringBuilder();
        Parallel.For(1, 5, () => new StringBuilder(), (x, option, sb) =>
        {
            sb.Append("line " + x + System.Environment.NewLine);
            MessageBox.Show("aaa"+x.ToString());
            return sb;
            
        }, sb =>
        {
            lock (result)
            {
                result.Append(sb.ToString());
            }
        });
    

    关于
    Parallel.XYZ
    的工作原理,存在一些误解

    评论中提到了一些重要的观点和建议,所以我不再重复。相反,我想分享一些关于并行编程的想法

    平行类 每当我们谈论并行编程时,我们通常区分两种:数据并行任务并行。前者在一个数据块上并行执行相同的函数。后者并行执行多个独立的功能

    (还有一种叫做管道的第三种模型,它是这两种模型的混合体。如果您对我建议搜索的模型感兴趣,我不会花时间在它上面。)

    并行类支持这两种模型。
    For
    ForEach
    是为数据并行而设计的,而
    Invoke
    是为任务并行而设计的

    分割 在数据并行的情况下,棘手的部分是如何分割数据以获得最佳吞吐量/性能。您必须考虑数据收集的大小、数据结构、处理逻辑和可用内核(以及其他许多方面)。因此,对于所有的建议,没有一条规则

    关于分区的主要关注点是不要使用资源不足(一些内核空闲,而另一些内核正在努力工作)和过度使用(等待的作业比可用的内核多得多,因此同步开销可能很大)

    让我们假设您的处理逻辑是稳定的(换句话说,各种输入数据不会显著改变处理时间)。在这种情况下,您可以在执行器之间对数据进行负载平衡。如果执行器完成,那么它可以获取要处理的新数据段

    您选择哪些数据应该发送到哪个执行器的方式可以由
    分区器定义。默认情况下.NET支持范围、区块、哈希和条带分区。有些是静态的(分区是在任何处理之前完成的),有些是动态的(取决于某些执行者可能比其他执行者接收到更多的处理速度)

    以下两篇优秀的文章可以让您更好地了解每个分区的工作原理:

    线程安全 如果每个执行者都可以执行其处理任务,而不需要与其他执行者交互,则认为它们是独立的。如果可以将算法设计为具有独立的处理单元,则可以最小化同步

    For
    ForEach
    的情况下,每个分区都可以有自己的分区本地存储。这意味着计算是独立的,因为中间结果存储在分区感知存储中。但像往常一样,您希望将它们合并到单个集合中,甚至合并到值中

    这就是为什么这些
    Parallel
    方法具有
    body
    localFinally
    的原因。前者用于定义单个处理,而后者是聚合和合并函数。(这有点类似于Map-Reduce方法)在后一种方法中,您自己已经知道了

    普林克 我不想探讨这个话题,它超出了问题的范围。但我想给大家一个起点:

    有用资源

    编辑:如何决定是否值得并行运行

    没有一个公式(至少据我所知)能告诉你什么时候使用并行执行有意义。正如我试图在分区部分强调的,分区是一个相当复杂的主题,因此需要进行一些实验和微调,以找到最佳解决方案

    我强烈建议您测量并尝试几种不同的设置

    以下是我的指导方针,您应该如何解决这一问题:

  • 尝试了解应用程序的当前特征
  • 执行几个不同的度量以发现执行瓶颈
  • 捕获当前解决方案的性能指标作为基线
  • 如果可能的话,尝试从代码库中提取这段代码,以简化微调
  • 试着用几个不同的方面和不同的投入来解决同一个问题
  • 测量它们并将它们与您的基线进行比较
  • 如果您对结果感到满意,那么将这段代码放入您的代码库中,并在不同的工作负载下再次测量
  • 尽可能多地获取相关指标
  • 如果可能的话,考虑执行(顺序和并行)解并比较它们的结果。
  • 如果你感到满意,那么就摆脱se
        lock (result)
        {
           result.Append(sb.ToString());
        }
    
        var result = new StringBuilder();
        Parallel.For(1, 5, () => new StringBuilder(), (x, option, sb) =>
        {
            sb.Append("line " + x + System.Environment.NewLine);
            MessageBox.Show("aaa"+x.ToString());
            return sb;
            
        }, sb =>
        {
            lock (result)
            {
                result.Append(sb.ToString());
            }
        });