初识Parallel Extensions之PLINQ
PLINQ查询处理模型
1 管道式(Pipelined Processing)
该模型是将查询线程(运行查询的线程)和枚举线程(进行迭代输出结果的线程)分开,处理器在有元素可用时就运行枚举将输出应用于foreach循环。也就是说不需要等到所有查询结果完成才进行枚举输出,只要查询结果能产生一个最终输出结果时就会进行枚举输出。简单说就是边查询边输出,这个模型的好处就是允许对输出做更多增量处理,从而减少为了存放结果所需的内存,坏处是由于中间结果需要更多的同步而降低性能。PLINQ缺省的是采用此模型。
2 准动态(stop-and-go Processing)
在这种模型下启动枚举的线程会联结所有其他线程来执行查询。在所有查询结果完成之后才进行枚举输出。这种模型的效率比管道式稍微高一些,因为这种模型需要同步的系统开销减少了。在对查询进行ToArray,ToList或者排序聚合操作时,系统将自动转为这种模型处理。因为这些操作都需要产生所有输出。具体在代码中是通过调用IParallelEnumerable接口的GetEnumerator的重载方法并且传递false参数来使用这种模型的,该方法如下:
IEnumerable GetEnumerator(bool usePipelining)
3 反转枚举(Inverted Enumeration)
该模型会为并行运行的PLINQ提供一个Lambda表达式,集合中的每个元素都运行一次。这是最高效的一种模型,因为它将高成本运算的控制反转给Lambda函数了。但注意的是在Lambda函数中不能使用共享状态,否则可能会导致系统崩溃,因为PLINQ不知道如何进行并发同步控制。但有些不同的是,此模型不能简单使用foreach循环,而必须使用特殊的ForAll API.例如:
string[] words = new[] { "Welcome", "to", "Beijing","OK","Hua","Ying","Ni" ,"2008"};
var lazyBeeQuery = from word in words.AsParallel() select word;
lazyBeeQuery.ForAll<string>(word => { Console.WriteLine(word); });
在我的机器上(双核)的输出结果是:
Hua
Welcome
Ying
Ni
2008
to
Beijing
OK
细心的人可能会发现其顺序和数组的顺序不同,这就是PLINQ并行运行的结果,可能在您的机器上可能结果又不同。
同时AsParallel重载方法提供一个参数来控制查询的并行度(就是多少个线程被用于查询),该方法定义如下:
public static IParallelEnumerable AsParallel(
IEnumerable source,int degreeOfParallelism)
如果你希望在使用管道式处理时有一个单独的线程专门用于枚举输出,你可以将degreeOfParallelism参数赋值为(Enviorment.ProcessCount-1)即可。