.NET4.0并行計算技術基礎(3)
這是一個系列講座,前面兩講的鏈接為:
在前面兩講中,基本上介紹完了并行計算基礎理論與相關概念,學習不是目的,應用才是。因此,本講將介紹一個并行計算的例子,并對.NET 4.0的并行擴展作一個總體的介紹。
======================================================
19.1.3 并行計算所帶來的挑戰
與串行執行的程序相比,開發并行程序需要軟件工程師具備一個“多線程”的大腦。我們先來看一個引例,初步體會一下如何使用
.NET 4.0
所提供的任務并行庫設計并行程序。
1 并行計算引例
請讀者仔細查看一下本節示例程序
SequentialvsParalled
的源碼。此程序完成了一個非常典型的數據處理工作:遞增一個整數數組的每個元素值。
示例程序將數組大小設定為
1000000
,然后對數組中的每個元素進行
100
次操作,每次操作都將元素值加
1
,因此,完成整個數據處理工作需要
108
次操作。
以下是串行代碼:
//
依次給一個數組中指定部分的元素執行
OperationCounterPerDataItem
次操作
static void IncreaseNumberInSquence(int[] arr,int startIndex,int counter)
{
for (int i = 0; i <counter; i++)
for (int j = 0; j < OperationCounterPerDataItem; j++)
arr[startIndex+i]++;
}
上述代碼在筆者的雙核筆記本電腦上執行時花費了
776
毫秒。
現在,使用
.NET 4.0
所提供的任務并行庫讓上述操作并行執行:
//
將任務劃分為
TaskCount
個子任務,然后并行執行
static void IncreaseNumberInParallel(int[] arr)
{
int counter = DataSize / TaskCount;
Parallel.For(0, TaskCount, i =>
{
int startIndex = i * counter;
IncreaseNumberInSquence(arr, startIndex, counter);
}
);
}
測試結果為
419
毫秒,并行加速系數約為
1.85
。
再改算法,將對每個元素的每個操作設定為一個任務,然后再并行執行:
static void IncreaseNumberInParallel2(int[] arr)
{
//
為每個數據項創建一個任務
Parallel.For(0, arr.Length, i =>
{
Parallel.For(0, OperationCounterPerDataItem, j => arr[i]++);
}
);
}
測試結果為
10057
毫秒,并行加速系數為
0.08
,比串行算法慢多了!
2 并行計算帶來的復雜性
上面所介紹的例子非常清晰地展示出并行程序設計的特殊性,并不是“并行”總比“串行”快的,到底怎樣才能獲得最大的并行加速系數,需要仔細地設計并行算法,并且應該在多個典型的軟硬件環境中進行對比測試,最終才能得到理想的并行設計方案。
開發并行程序的關鍵在于要找到一個合適的任務分解方案,并行總要付出一定的代價,比如線程同步、線程通訊、同步緩沖數據等都是開發并行程序必須認真考慮的問題。
下表對比了并行程序與串行程序的主要差別:
項目
|
串行程序
|
并行程序
|
程序行為特性
|
可以預期的,相同運行環境下總可以得到相同的結果
|
如果沒有提供特定的同步手段,則程序執行的結果無法預期
|
內存訪問
|
獨占訪問內存單元,數據可靠
|
有可能因多線程同時存取同一內存單元而引發數據存取錯誤
|
鎖
|
不需要
|
必須為共享資源加鎖
|
死鎖
|
不可能出現
|
可能出現,需要仔細考慮程序中可能出現的種種情況予以避免
|
測試
|
使用代碼覆蓋的測試方法可以檢測出絕大多數
BUG
|
由于多個線程同時并行,僅使用代碼覆蓋的測試方法無法檢測出程序中隱藏的
BUG
,并行程序的測試變得很復雜
|
調試
|
相對簡單,可以隨時停止程序運行,單步跟蹤定位到每條語句和每個變量的值
|
由于多個線程同時運行,當你暫停一個線程進行調試時,其他線程可能還在運行中,因此無法保證調試環境的一致性,并行程序的調試非常困難。
|
正因為并行程序開發、測試和調試都比串行程序要困難,所以一般都是先編寫程序的串行版本,等其工作正常之后再將其升級替換為并行版本。
3 何時使用“并行計算”?
根據前面的介紹,讀者一定對“并行計算”有了個總體的認識,由于“并行”需要付出代價,因此,不是所有的程序都需要轉換為并行的,當要處理的數據量很大,或者要執行的數據處理任務繁重,并且這些任務本身就可以分解為互不相關的子任務時,使用并行計算是合適的。
對于哪些規模較小的數據處理任務,比如你要編寫一個“通訊簿”小程序來保存和檢索好友信息,就不必考慮并行處理了,因為要處理數據量不會很大,串行算法的性能就可以滿足需求,還用“并行處理”就顯得是“牛刀殺雞”。除了增加程序開發難度之外沒有什么好處。
19.2 .NET 4.0 中的并行計算組件
由于并行計算是將一個工作任務進行分解以并發執行,因此,任何一個支持并行計算的軟件開發與運行平臺都必須解決這些并發執行的子任務之間的相互協作問題,比如:
l
一個子任務需要等待其它子任務的完成,多個子任務完成之后才允許執行下一個子任務(即所謂
fork-join
),
l
一個子任務結束后自動啟動多個下級子任務的執行
l
允許一個任務中途取消
l
……
.NET 4.0
通過對已有的基類庫進行擴充和增強(
圖
19
?
7
),滿足了上述需求。
如
圖
19
?
7
所示,
.NET 4.0
給
“
System.Threading
”
命名空間增加了一些新的類(,比如在第
17
章介紹過的
Barrier
等幾個新的線程同步類),同時對部分已有類也進行了調整和優化。另外,針對中途取消線程或作務執行這一實際開發中非常普遍的需求,提供了一個統一取消模型(本書第
16
章介紹了此模型)。最大的變化是
.NET
為基類庫提供了多個與并行計算密切相關的類,并將它們統一稱之為“
并行擴展(
Parallel Extensions
)
”。
如
圖
19
?
8
所示,
NET 4.0
“并行擴展”的主要包括以下幾個部分:
1
并行語言集成查詢(
PLINQ
,
Parallel Language Integrated Query
)
,這是
.NET 3.0
引入的
LINQ to Object
(本書第
24
章介紹)的換代“產品”,讓查詢操作可以并行執行。
2
任務并行庫(
TPL
,
Task Parallel Library
):
將開發并行程序的抽象級別從“線程(
thread
)”提升到“任務(
Task
)”,只需規定好計算機要執行的任務,然后由
.NET
去管理線程的創建和同步等問題。
3
同步的數據結構(
CDS
,
Coordination Data Structures
):
包括一組線程安全的常用數據結構,比如線程安全的隊列、堆棧等,在并行程序中訪問這些數據結構,可以不需要顯式地使用
lock
。
4
任務調度器(
Task Scheduler
):
負責任務的創建、執行、暫停等管理工作。
5
線程池:
.NET 4.0
對原有的托管線程池功能進行了大幅度的增強,通過給其集成一個任務調度器,線程池中的線程可以高效地并行執行各種任務。
上述五個組成部分當中,
PLINQ
是建立在
TPL
之上的
,
而
Task Scheduler
是并行計算的核心,是一個
Runtime
,它與線程池相集成,負責將任務分派給線程池中的各個線程執行。
下面幾個小節中,我們就整個
.NET 4.0
并行擴展中與軟件工程師關聯最緊密的兩個主要組成部分——任務并行庫
TPL
和并行語言集成查詢
PLINQ
的內部機理進行剖析,然后在此基礎上詳細介紹使用
TPL
和
PLINQ
開發并行程序的基本技巧。
========================================
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
