所以主题是问题。
我得到那个方法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>
,但它的代码太复杂了。
答案 0 :(得分:2)
最后在我的第二次 PLINQ攻击中,我找到了答案。而且非常清楚。
问题是枚举器并不简单。这是一个特殊的multithreading
。它是如何工作的?答案是enumerator
不返回源的下一个值,它返回下一个分区的总和。因此,当Environment.ProcessorCount
enumerator.MoveNext
内的 执行实际求和工作时,此代码仅执行2,4,6,8 ...次(基于enumerator.OpenQuery
)方法 。
因此,TPL明确地将源可分区分区,然后独立地对每个分区求和,然后对此求和进行求解,请参阅IntSumAggregationOperatorEnumerator<TKey>
。这里没有魔法,只能深入了解。
答案 1 :(得分:1)
Sum
运算符聚合单个线程中的所有值。这里没有多线程。诀窍是多线程正在其他地方发生。
PLINQ Sum
方法可以处理PLINQ枚举。这些枚举可以使用其他构造(例如where)来构建,这些构造允许在多个线程上处理集合。
Sum运算符始终是链中的最后一个运算符。虽然可以在多个线程上处理这个总和,但是TPL团队可能发现这对性能有负面影响,这是合理的,因为这个方法唯一要做的就是添加一个简单的整数。
因此,此方法处理来自其他线程的所有结果,并在单个线程上处理它们并返回该值。真正的诀窍在于其他PLINQ扩展方法。
答案 2 :(得分:-2)
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会依次执行它的innerscope。
试试这个
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);
});