多核时代 .NET Framework 4 中的并行编程6---并行LINQ
1. 并行LINQ(PLINQ)
并行 LINQ (PLINQ) 是 LINQ 模式的并行实现。 PLINQ 查询在许多方面类似于非并行 LINQ to Objects 查询。 PLINQ 尝试充分利用系统中的所有处理器, 它利用所有处理器的方法是,将数据源分成片段,然后在多个处理器上对单独工作线程上的每个片段并行执行查询。 在许多情况下,并行执行意味着查询运行速度显著提高。
PLINQ有两个重要的类:ParallelEnumerable和ParallelQuery.其中, ParallelEnumerable包含许多在ParallelQuery类型上进行操作的扩展方法. ParallelEnumerable同样实现许多在上Enumerable的方法,因为 ParallelQuery 实现IEnumerable接口,使用我们可以很方便的创建ParallelQuery类的一个实例并运用在ParallelEnumerable上实现的并行方法.接下来,通过代码来学习这些重要的类.
2. ParallelEnumerable 运算符
ParallelEnumerable包含很多运算符,他们对下封装很多复杂的实现过程, 以便对上我们可以方便的使用并行linq查询.具体如下:
(1) AsParallel
PLINQ 的入口点。指定如果可能,应并行化查询的其余部分。PLINQ 通常只需向数据源添加 AsParallel 查询操作,即可在某些查询类型的旧版代码上获得显著的性能改进。代码如下:
int[] source = new int[1000];
for (int i = 0; i < source.Length; i++)
{
source[i] = i;
}
var presult = source.AsParallel().Select(c => Math.Pow(c, 3));
foreach (var d in presult)
{
Console.WriteLine("AsParallel Result is:{0}", d);
}
通过上面的代码,我们可以通过使用AsParallel()扩展方法,即可开启并行查询.因此,一般的linq查询代码我们只需要使用AsParallel()扩展的方法,即可启用并行查询.
(2) AsSequential<TSource>
指定查询按顺序运行,也就是将并行的查询转换成一般的顺序查询.代码如下:
int[] source = new int[1000];
for (int i = 0; i < source.Length; i++)
{
source[i] = i;
}
var result = source.AsParallel().AsSequential().Where(c => c > 0);
foreach (var d in result)
{
Console.WriteLine("AsSequential Result:{0}", d);
}
(3) AsOrdered
指定 PLINQ 应保留查询的其余部分的源序列排序,直到例如通过使用 orderby子句更改排序为止。但是,因为并行查询的情况,默认未排序的,除非顺序特别重要,否则从性能角度的考虑建议不要排序.
int[] sourceData = new int[5];
for (int i = 0; i < sourceData.Length; i++)
{
sourceData[i] = i;
}
var results1 = sourceData.AsParallel().AsOrdered().Select(c => Math.Pow(c, 3));
foreach (double d in results1)
{
Console.WriteLine("AsOrdered result {0} ", d);
}
(4) AsUnordered<TSource>
指定查询的其余部分的 PLINQ 不需要保留源序列的排序。
(5) WithCancellation<TSource>
指定 PLINQ 应定期监视请求取消时提供的取消标记和取消执行的状态,这样我们就可以在需要时候取消查询。代码如下:
CancellationTokenSource token = new CancellationTokenSource();
int[] sourceData = new int[1000000];
for (int i = 0; i < sourceData.Length; i++)
{
sourceData[i] = i;
}
var results1 = sourceData.AsParallel().WithCancellation(token.Token).WithMergeOptions(ParallelMergeOptions.NotBuffered).Where(c => c > 1);
Task.Factory.StartNew(() =>
{
Thread.Sleep(5000);
token.Cancel();
Console.WriteLine("Token source cancelled");
});
try
{
foreach (double d in results1)
{
Console.WriteLine("CancellationTokenSource result {0},Thread Id:{1} ", d, Thread.CurrentThread.ManagedThreadId);
}
}
catch (OperationCanceledException)
{
Console.WriteLine("Caught cancellation exception");
}
Console.WriteLine("Press enter to finish");
Console.ReadLine();
(6) WithDegreeOfParallelism<TSource>
指定 PLINQ 应当用来并行化查询的处理器的最大数目。并行度是将用于处理查询的同时执行的任务的最大数目. 代码如下:
var results1 = sourceData.AsParallel().WithDegreeOfParallelism(3).Where(c => c > 1);
我们不防想想,如果将WithDegreeOfParallelism设置为1,会是什么情况呢?
(7) WithMergeOptions<TSource>
提供有关 PLINQ 应当如何(如果可能)将并行结果合并回到使用线程上的一个序列的提示, 指定查询对输出进行缓冲处理的方式.
var results1 = sourceData.AsParallel().WithMergeOptions(ParallelMergeOptions.NotBuffered).Where(c => c > 1);
其中ParallelMergeOptions枚举可选择以下几个值:
Ø Default 使用默认合并类型,即 AutoBuffered。
Ø NotBuffered 不利用输出缓冲区进行合并。一旦计算出结果元素,就向查询使用者提供这些元素。
Ø AutoBuffered 利用系统选定大小的输出缓冲区进行合并。 在向查询使用者提供结果之前,会先将结果累计到输出缓冲区中。
Ø FullyBuffered 利用整个输出缓冲区进行合并。 在向查询使用者提供任何结果之前,系统会先累计所有结果。
(8) WithExecutionMode<TSource>
指定 PLINQ 应当如何并行化查询(即使默认行为是按顺序运行查询)。
var results1 = sourceData.AsParallel().WithExecutionMode(ParallelExecutionMode.Default).Where(c => c > 1);
其中, ParallelExecutionMode枚举可以选择如下两种值:
Ø Default 这是默认设置。 PLINQ 将检查查询的结构,仅在可能带来加速时才对查询进行并行化。 如果查询结构指示不可能获得加速,则 PLINQ 会将查询当作普通的 LINQ to Objects 查询来执行。
Ø ForceParallelism 并行化整个查询,即使要使用系统开销大的算法。 如果认为并行执行查询将带来加速,则使用此标志,但处于默认模式的 PLINQ 将按顺序执行它。
(9) ForAll<TSource>
多线程枚举方法,与循环访问查询结果不同,它允许在不首先合并回到使用者线程的情况下并行处理结果。这是处理并行化查询输出的高效方法,因为它不需要在结束时执行合并步骤.代码如下:
int[] sourceData = new int[5];
for (int i = 0; i < sourceData.Length; i++)
{
sourceData[i] = i;
}
sourceData.AsParallel().ForAll((d) =>
{
Console.WriteLine("ForAll result {0} ", d);
}
(10) Aggregate 重载
对于 PLINQ 唯一的重载,它启用对线程本地分区的中间聚合以及一个用于合并所有分区结果的最终聚合函数。
3. 延迟执行
默认情况下PLINQ和LINQ类似,它只在需要查询结果时才计算.为了快速得到结果,我们可以在查询之后再调用ToArray(), ToDictionary(),ToList()方法,以便使其立刻计算.
4. 异常处理
异常的处理还是使用AggregateException类处理.具体内容可以查看之前的”异常处理”章节.
5. Range方法
ParallelEnumerable类还提供了Rang扩展方法. 它可生成指定范围内的整数的并行序列,也就是在指定的范围内进行并行处理.代码如下:
var result1 =
from e in ParallelEnumerable.Range(0, 100)
where e % 2 == 0
select Math.Pow(e, 2);
6. Repeat方法
ParallelEnumerable类还提供了Repeat扩展方法. 生成包含一个重复值的并行序列,也就是并行生成重复值.代码如下:
var result2 =
ParallelEnumerable.Repeat(10, 1000)
.Select(item => Math.Pow(item, 2));
此次,有关PLINQ的基本操作介绍完毕,后续还有其他内容继续介绍.