19.4.2 基於PLINQ開發
因爲
PLINQ
創建於
LINQ
基礎之上,實現了全部標準
LINQ
運算符的並行版本,所以,本節只介紹
PLINQ
中不一樣於
LINQ
的技術特徵。讀者須要先掌握好編寫標準
LINQ
查詢語句的基本技巧,才能夠掌握本節介紹的內容。
1 將LINQ查詢轉爲並行執行
在
LINQ
查詢語句中,在一個能夠返回數據集合的子句後面大均可以添加一個
AsParallel()
或
AsParallel<T>()
子句將其轉換爲
PLINQ
語句。
如下是一個例子,從一個整數集合中取出全部的偶數
List<int> lst = new List<int>();
//向lst中追加數據,代碼略...
var evenNums = from num in lst.AsParallel<int>()
where num%2==0
select num;
上述代碼執行時,
TPL
引擎會自動在後臺建立並管理線程,讓查詢得以並行執行。
然而,在某些狀況下,因爲並行處理會帶來錯誤的結果,所以必須強制將其轉爲串行模式,這時,能夠調用
ParallelEnumerable
類的擴展方法
AsSequential()
達到此目的。
請看示例
AsParallelAndAsSequential
。
示例程序在一個保存了學生考試成績的數據集合中查找
60
分以上的學生,而且按照成績高低排名次。
若是使用
PLINQ
來完成這個工做,咱們能夠寫出如下代碼:
int counter = 0;//計數器
var query =
from student in students.AsParallel()
where student.Score > 60 //分數大於60
orderby student.Score descending //按成績降序排序
select new //返回學生信息
{
TempID = ++counter, //學生成績名次
student.Name,
student.Score
};
//輸出處理結果,代碼略……
請注意上述代碼中加了方框的部分,因爲上述
PLINQ
查詢是並行執行的,這就是說會有多個線程同時訪問
counter
變量,這就隱含着一個「多線程同時訪問共享資源」的問題,並且到底
TPL
會建立多少個線程,以及這些線程的推動順序是不可控的,所以,上述代碼執行結果是錯誤的,學生成績與名次不能正確對應。
若是將上述代碼中的
AsParallel
子句去掉,則結果正確,但卻失去了並行執行的任何好處。
如何能在享受
PLINQ
所帶來的性能提高的好處的同時,又能避免因並行執行而獲得錯誤的結果?
其關鍵在於要分清楚數據處理工做中哪些能夠並行執行,哪些必須串行執行,而後,再將其組合起來。
對於這種須要混合並行與串行執行的狀況,直接使用
PLINQ
語句比較困難,一般在這種場景下使用擴展方法實現。
在本例中,能夠寫出如下代碼同時組合「並行執行」與「串行執行」的兩種數據處理工做。
var query2 = students.AsParallel() //使用並行查詢
.Where(student => student.Score > 60) //分數大於60
.OrderByDescending(stu => stu.Score) //按成績降序排序
.AsSequential() //強制轉換爲串行執行
.Select(studentInfo =>
new
{
TempID = ++counter, //名次
studentInfo.Name,
studentInfo.Score
});
上述代碼中,按成績篩選記錄是並行執行的,而生成處理結果集合時是順序執行的。
2 維持數據的順序
默認狀況下,
PLINQ
查詢要處理的數據被認爲「順序可有可無」,
TPL
會按照它內置的算法將數據分紅幾組(稱爲「數據分區」),而後在這些相互獨立的數據分區上並行處理。
然而在某些狀況下,數據的順序是重要的,請看示例項目
AsOrdered
。
示例項目先用隨機整數填充了一個
List<int>
集合對象
source
,而後,程序找出排在最前面的
10
個偶數出來,要求保持原有順序。
如下
PLINQ
查詢完成這個工做。
var parallelQuery = from num in source.AsParallel().AsOrdered()
where num % 2 == 0
select num;
var First10Numbers = parallelQuery.Take(10);
上述查詢語句中的
AsOrdered()
子句強制
PLINQ
保持原始數據的排列次序。
讀者能夠試一下,若是去掉
AsOrdered()
子句,則獲得的結果是錯誤的。
AsOrdered()
是
ParallelEnumerable
類提供的靜態擴展方法,所以適用於絕大多數數據集合類型。
注意:
AsOrdered()
和前面介紹的AsSequential()是不同的,AsSequential()強制PLINQ查詢以串行方式執行,而AsOrdered()還是並行執行的,只不過並行執行的結果先被緩存起來,而後再按原始數據順序進行排序,才獲得最後的結果。
很明顯,給
PLINQ
查詢加上
AsOrdered()
子句將會影響到程序的性能,所以,儘可能避免使用它。
在一些狀況下,能夠經過修改
PLINQ
查詢的順序避免使用
AsOrdered()
子句。例如,假設整數集合中的原始是排好序的,則如下
PLINQ
查詢按順序取出全部的偶數:
var evenNums = from num in source.AsParallel().AsOrdered()
where num % 2 == 0
select num;
若是對查詢操做的順序進行一下修改,會獲得更好的性能:
var evenNums = from num in source.AsParallel()
where num % 2 == 0
orderby num
select num;
固然,咱們並不能確定「修改以後的代碼必定比修改前快」,由於這取決於許多因素,特別是「
TPL
執行
PLINQ
查詢內部所使用的數據分區策略和並行算法」,它對於應用軟件開發工程師而言是不可控的因素,但卻對性能影響很大。此處只是提醒讀者在編碼時須要注意這些細節。