【问题标题】:Ordered PLINQ ForAll订购 PLINQ ForAll
【发布时间】:2011-07-18 04:08:32
【问题描述】:

关于 order preservation in PLINQ 的 msdn 文档说明了以下关于 ForAll() 的内容。

  • 源序列有序时的结果:非确定性并行执行
  • 源序列无序时的结果:非确定性并行执行

这是否意味着永远无法保证ForAll 方法的有序执行?

我以前没有使用过 PLINQ,但下面的 Code Review question 似乎是它的合适用法。在我的答案的底部,我写道:

Events.AsParallel().AsOrdered().ForAll( eventItem =>
{
    ...
} );    

阅读文档后,我相信AsOrdered() 不会改变任何东西?
我还怀疑前面的查询不能替换简单的for 循环,其中顺序很重要?
可能还会并行调用StringBuilder,导致输出错误?

【问题讨论】:

  • 如果代码顺序发生,并行 linq 有什么用?

标签: c# plinq


【解决方案1】:

顺序保存通常只应用于results - 即输入可以按任何顺序处理,但返回按原始顺序.

由于ForAll 没有返回任何内容,因此我知道它实际上没有任何影响。

将排序应用于处理的唯一方法是在处理第 1 项之前完成第 0 项,在处理第 2 项之前等...此时您没有并行性。

【讨论】:

  • K,所以我的怀疑是正确的。感谢您的解释。我相信在给定示例中,当所有字符串可以单独解析然后按顺序合并时,并行性仍然应该是一个选项。什么是最合适的构造?
  • Steven:您可以只使用 PLINQ 进行解析,使其按顺序给出结果,然后使用普通的 foreach 循环进行实际的附加。
【解决方案2】:

正如其他人正确回答的那样,ForAll 方法永远不能保证以任何特定顺序对可枚举元素执行操作,并且会默默地忽略 AsOrdered() 方法调用。

为了让读者有正当理由以接近原始顺序的方式对可枚举元素执行操作(在并行处理上下文中尽可能合理),下面的扩展方法可能会有所帮助。

public static void ForAllInApproximateOrder<TSource>(this ParallelQuery<TSource> source, Action<TSource> action) {

    Partitioner.Create( source )
               .AsParallel()
               .AsOrdered()
               .ForAll( e => action( e ) );

}

然后可以按如下方式使用:

orderedElements.AsParallel()
               .ForAllInApproximateOrder( e => DoSomething( e ) );

需要注意的是,上述扩展方法使用 PLINQ ForAll 而不是 Parallel.ForEach,因此继承了 PLINQ 内部使用的线程模型(这与 Parallel.ForEach 使用的不同——在默认情况下不那么激进)我的经验)。下面是使用Parallel.ForEach的类似扩展方法。

public static void ForEachInApproximateOrder<TSource>(this ParallelQuery<TSource> source, Action<TSource> action) {

    source = Partitioner.Create( source )
                        .AsParallel()
                        .AsOrdered();

    Parallel.ForEach( source , e => action( e ) );

}

然后可以按如下方式使用:

orderedElements.AsParallel()
               .ForEachInApproximateOrder( e => DoSomething( e ) );

使用上述任一扩展方法时,无需将AsOrdered() 链接到您的查询,无论如何它都会在内部调用。

我发现这些方法在处理具有粗粒度意义的元素时很有用。例如,它可以用于处理从最旧开始并朝着最新的记录。在许多情况下,不需要记录的确切顺序 - 只要较旧的记录通常在较新的记录之前得到处理。同样,可以处理具有低/中/高优先级的记录,这样在大多数情况下,高优先级记录将在低优先级记录之前处理,边缘情况紧随其后。

【讨论】:

    【解决方案3】:

    AsOrdered() 不会改变任何东西 - 如果你想对并行查询的结果强制执行顺序,你可以简单地使用 foreach() ForAll()利用并行性,这意味着一次对集合中的多个项目执行副作用。实际上排序只适用于查询的结果(结果集合中项目的顺序),但这与ForAll() 无关,因为ForAll() 根本不影响顺序。

    在 PLINQ 中,目标是最大化 性能,同时保持 正确性。查询应该运行为 尽可能快,但仍然产生 正确的结果。在某些情况下, 正确性要求保留源序列的顺序

    请注意,ForAll() 不会转换集合(它不是即投影到新集合),它纯粹是为了对 PLINQ 查询的结果执行副作用。

    【讨论】:

      【解决方案4】:

      这是否意味着永远无法保证 ForAll 方法的有序执行?

      是的 - 不保证订单。

      并行化意味着将工作分配给不同的线程,然后将它们各自的输出组合起来。

      如果您需要对输出进行排序,则不要使用 PLinq - 或添加一些稍后的步骤来重新排序。


      另外,如果您在 plinq 执行中访问像 StringBuilder 这样的对象,请确保这些对象是线程安全的 - 并且还要注意这种线程安全实际上可能使 plinq 比非并行 linq 慢。

      【讨论】:

        【解决方案5】:

        现在作为扩展方法:

        它将在多个核心上进行处理,然后对结果进行排序,因此存在排序的开销。这是answer on benchmarking simple for vs parallel

         public static IEnumerable<T1> OrderedParallel<T, T1>(this IEnumerable<T> list, Func<T, T1> action)
            {
                var unorderedResult = new ConcurrentBag<(long, T1)>();
                Parallel.ForEach(list, (o, state, i) =>
                {
                    unorderedResult.Add((i, action.Invoke(o)));
                });
                var ordered = unorderedResult.OrderBy(o => o.Item1);
                return ordered.Select(o => o.Item2);
            }
        

        像这样使用:

        var result = Events.OrderedParallel(eventItem => ...);
        

        希望这会为您节省一些时间。

        【讨论】:

          【解决方案6】:

          ForAll 在多个线程中并行运行该操作。在任何给定时刻,多个动作将同时运行,在这些情况下,“顺序”的概念不适用。要按顺序运行操作,您必须按顺序运行它们,最简单的方法是在单个线程中运行它们。这可以通过在标准foreach 循环中枚举查询结果来实现:

          var query = Events.AsParallel().AsOrdered();
          foreach (var eventItem in query)
          {
              // do something with the eventItem
          }
          

          如果你更喜欢流畅的ForAll语法,你可以在你的项目中添加一个静态类,使用下面的ForEach扩展方法:

          public static void ForEach<TSource>(this IEnumerable<TSource> source,
              Action<TSource> action)
          {
              foreach (TSource item in source)
              {
                  action(item);
              }
          }
          

          并像这样使用它:

          Events.AsParallel().AsOrdered().ForEach(eventItem =>
          {
              // do something with the eventItem
          });
          

          应该注意的是,在给定的示例中,并行 LINQ 的使用是多余的。查询Events.AsParallel().AsOrdered() 不执行对源可枚举的转换,因此没有进行实际计算。您可以删除 .AsParallel().AsOrdered() 部分并获得相同的结果。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-08-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多