繁体   English   中英

AsParallel扩展如何实际工作

[英]How AsParallel extension actually works

所以主题是问题。

我得到的方法AsParallel返回使用相同LINQ关键字的包装器ParallelQuery<TSource> ,但是从System.Linq.ParallelEnumerable而不是System.Linq.Enumerable

这很清楚,但是当我查看反编译源时,我不明白它是如何工作的。

让我们从最简单的扩展开始:Sum()方法。 码:

[__DynamicallyInvokable]
public static int Sum(this ParallelQuery<int> source)
{
  if (source == null)
    throw new ArgumentNullException("source");
  else
    return new IntSumAggregationOperator((IEnumerable<int>) source).Aggregate();
}

很明显,让我们去Aggregate()方法。 它是InternalAggregate方法的一个包装器,可以捕获一些异常。 现在让我们来看看它。

protected override int InternalAggregate(ref Exception singularExceptionToThrow)
{
  using (IEnumerator<int> enumerator = this.GetEnumerator(new ParallelMergeOptions?(ParallelMergeOptions.FullyBuffered), true))
  {
    int num = 0;
    while (enumerator.MoveNext())
      checked { num += enumerator.Current; }
    return num;
  }
}

这是一个问题:它是如何工作的? 我发现变量没有并发安全性,由许多线程修改,我们只看到迭代器和求和。 这是魔术调查员吗? 或者它是如何工作的? GetEnumerator()返回QueryOpeningEnumerator<TOutput> ,但它的代码太复杂了。

最后,在我的第二次PLINQ攻击中,我找到了答案。 而且非常清楚。 问题是枚举器并不简单。 这是一个特殊的multithreading 它是如何工作的? 答案是enumerator器不返回源的下一个值,它返回下一个分区的整数。 因此,此代码才会执行2,4,6,8 ...倍(基于Environment.ProcessorCount ),当实际求和工作内部进行enumerator.MoveNextenumerator.OpenQuery方法

因此,TPL明确地将源可分区分区,然后独立地对每个分区求和,然后对此求和进行求解,请参阅IntSumAggregationOperatorEnumerator<TKey> 这里没有魔法,可能会更深入。

Sum运算符聚合单个线程中的所有值。 这里没有多线程。 诀窍是多线程正在其他地方发生。

PLINQ Sum方法可以处理PLINQ枚举。 这些枚举可以使用其他构造(例如where)来构建,这些构造允许在多个线程上处理集合。

Sum运算符始终是链中的最后一个运算符。 虽然可以在多个线程上处理这个总和,但是TPL团队可能发现这对性能有负面影响,这是合理的,因为这个方法唯一要做的就是添加一个简单的整数。

因此,此方法处理来自其他线程的所有结果,并在单个线程上处理它们并返回该值。 真正的诀窍在于其他PLINQ扩展方法。

protected override int InternalAggregate(ref Exception singularExceptionToThrow)
{
  using (IEnumerator<int> enumerator = this.GetEnumerator(new ParallelMergeOptions?    (ParallelMergeOptions.FullyBuffered), true))
  {
    int num = 0;
    while (enumerator.MoveNext())
      checked { num += enumerator.Current; }
    return num;
  }
}

这段代码不会被并行执行,while会依次执行它的内窥镜。

试试这个

        List<int> list = new List<int>();

        int num = 0;

        Parallel.ForEach(list, (item) =>
            {
                checked { num += item; }
            });

内部操作将在ThreadPool上传播,并且在处理完所有项目时,ForEach语句将完成。

在这里你需要线程安全:

        List<int> list = new List<int>();

        int num = 0;

        Parallel.ForEach(list, (item) =>
            {
                Interlocked.Add(ref num, item);
            });

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM