并行Linq
.Net 4在 System.Linq 名称空间中包含一个新类 ParallelEnumerable,可以分解查询的工作使其分布在多个线程上。尽管 Enumerable 类给 IEnumerable<T> 接口定义了扩展方法,但 ParallelEnumerable 类的大多数扩展方法是 Parallel<TSource> 类的扩展。一个重要的例外是 AsParallel()方法,它扩展了 IEnumerable<TSource> 接口,返回 ParallelQuery<TSource> 类,所以正常的集合类可以以平行方式查询。
并行查询
为了说明并行LINQ,需要一个大型集合。对于可以放在CPU的缓存中的小集合,并行LINQ看不出效果。在下面的代码中,用随机值填充一个大型的 int 数组:
const int arraySize = 100000000; var data = new int[arraySize]; var r = new Random ( ); for ( int i = 0; i < arraySize; i++ ) { data[i] = r.Next ( 40 ); }
现在可以使用LINQ查询筛选数据,获取所筛选数据的总和。该查询用 where 子句定义了一个筛选器,仅汇总对应值小于20的项,接着调用聚合函数 Sum()方法。与平时的LINQ查询的唯一区别是,这次调用了 AsParallel() 方法。
var sum = ( from x in data.AsParallel ( ) where x < 20 select x ).Sum ( );
与平时LINQ查询一样,编译器会修改语法,以调用 AsParallel()、Where()、Select()和Sum()方法。AsParallel()方法用 ParallelEnumerable 类定义,以扩展 IEnumerable<T> 接口,所以可以对简单的数组调用它。AsParallel()方法返回 ParallelQuery<TSource>。因为返回的类型,所以编译器选择的Where()方法是 ParallelEnumerable.Where(),而不是 Enumerable.Where() 。在下面的代码中, Select()和 Sum()方法也来自 ParallelEnumerable 类。与Enumerable类的实现代码相反,对于ParallelEnumerable类,查询是分区的,以便 多个线程可以同时处理该查询。数组可以分为多个部分,其中每个部分由不同的线程处理,以筛选其余项。完成分区的工作后,就需要合并,获得所有部分的总和。
var sum = data.AsParallel ( ).Where ( x => x < 20 ).Select ( x => x ).Sum ( );
运行这行代码会启动任务管理器,这样就可以看出系统的所有CPU都在忙碌。如果删除AsParallel()方法,就不可能使用多个CPU。当然,如果系统上没有多个CPU,就不会看到并行版本带来的改进。
分区器
AsParallel()方法不仅扩展了 IEnumerable<T>接口,还扩展了Partitioner类。通过它,可以影响要创建的分区。
Partitioner类用System.Collection.Concurrent名称空间定义,并且有不同的变体。Create()方法接受实现了 IList<T>类的数组或对象。根据这一点,以及类型的参数 loadBalance 和该方法的一些重载版本,会返回一个不同的Partitioner类型。对于数组, .NET 4 包含派生自抽象基类OrderablePartitioner<TSource>的DynamicPartitionerForArray<TSource>类和StaticPartitionerForArray<TSource>类和StaticParitionerForArray<TSource>类。
修改上面一小节的代码,手工创建一个分区器,而不是使用默认的分区器:
var sum = ( from x in Partitioner.Create ( data, true ).AsParallel ( ) where x < 20 select x ).Sum ( );
也可以调用WithExecutionMode()和WithDegreeOfParallelism()方法,来影响并行机制。对于WithExecutionMode()方法可以传递ParallelExecutionMode()的一个Default值或者ForceParallelism值。默认情况下,并行LINQ避免使用系统开销很高的并行机制。对于WithDegreeOfParallelism()方法,可以传递一个整数值,以指定应并行运行的最大任务数。