初识Parallel Extensions之PLINQ
今天我们就来谈谈平行扩展的关键组件之一PLINQ(Parallel LINQ)。微软对PLINQ在Parallel FX中的定位是:PLINQ是TPL(Task Parallel Library)的一个高层应用。由于目前微软对TPL研发的时间还比较短,这个社区预览版的TPL版本的质量还是比较低的,而且微软发布这个版本的目的也是为了更好的获得开发社区的反馈信息,为了让PLINQ有更高的质量,所以目前PLINQ还是基于ThreadPool的实现,而不是基于TPL的API的。不过这只是内部实现不同而已,以后正式发布的时候PLINQ的对外接口的变更应该不会太大。
如何使用PLINQ?
1 添加System.Threading.dll到引用中
2 通过调用System.Linq.ParallelQuery.AsParallel扩展方法,将数据封装到IParallelEnumerable中。
基于声明方式的数据并行性
调用AsParallel扩展方法可使得编译器使用System.Linq.ParallelEnumerable版本的查询运算符,而不是System.Linq.Enumerable的。熟悉LINQ的人都知道,查询表达式在编译时都将转化成对扩展方法的调用。对于LINQ而言,所有的扩展方法都封装在System.Linq.Enumerable静态类中,该类定义的都是针对IEnumerable数据源的扩展方法。而对于PLINQ,所有的扩展方法都封装在System.Linq.ParallelEnumerable静态类中,而且该类针对的都是IParallelEnumerable数据源的扩展方法,是System.Linq.Enumerable静态类扩展方法的镜像,只不过是通过并行方式对查询进行评估。IParallelEnumerable接口从IEnumerable接口继承,所以PLINQ也具有LINQ的延迟执行的特点,以及执行foreach。
为了让大家清晰知道系统是如何将使用System.Linq.Enumerable版本的查询运算符变成System.Linq.ParallelEnumerable版本的,我们先来看看System.Linq.ParallelQuery.AsParallel方法:
public static IParallelEnumerable AsParallel(IEnumerable source)
很显然这个方法就是将IEnumerable的数据源转化成IParallelEnumerable以使得使用平行版本的运算。这就是平行架构中通过AsParallel的声明来使用并行使用数据的方式,也是PLINQ的编程模型。
所以这种基于声明方式的数据并行性使得从LINQ到PLINQ的转化非常容易,例如我们有这样的LINQ代码片段:
string[] words = new[] { "Welcome", "to", "Beijing" };
(from word in words select Process(word)).ToArray();
我们很容易就可以将其变成PLINQ版本:
string[] words = new[] { "Welcome", "to", "Beijing" };
(from word in words.AsParallel() select Process(word)).ToArray();
当然,如果你是通过查询操作(就是直接调用静态扩展函数),而不是使用查询表达式(有时候查询表达式没有提供相应的表达式语句,例如C#3.0中没有提供Skip和Take相对应的查询表达式语句,我们只能通过直接调用查询操作函数)的情况下,将LINQ迁移到PLINQ,我们除了要调用AsParallel方法,还需要将直接调用Enumerable的方法改成对ParallelEnumerable的调用,例如:
IEnumerable data = ...;
var q = Enumerable.Select(Enumerable.OrderBy(
Enumerable.Where(data, (x) => p(x)),(x) => k(x)),(x) => f(x));
foreach (var e in q) a(e);
要使用 PLINQ,必须按如下方式重新编写该查询:
IEnumerable data = ...;
var q = ParallelEnumerable.Select(ParallelEnumerable.OrderBy(
ParallelEnumerable.Where(data.AsParallel(), (x) => p(x)),
(x) => k(x)),(x) => f(x));
foreach (var e in q) a(e);
注意:有些查询运算符是二元的,使用两个IEnumerable作为输入参数(例如Join),最左边数据源的类型决定了使用LINQ还是PLINQ,因此你只需要在第一个数据源上调用AsParallel便能使查询并行查询,例如:
IEnumerable leftData = ..., rightData = ...;
var q = from x in leftData.AsParallel()
join y in rightData on x.a == y.b
select f(x, y);