呵呵,越到國慶反而越忙,好多天沒更新了,工作第一天,貼出一篇新文。
金旭亮
2009.10.9
=======================================
.NET4.0并行計算技術基礎(7)
前幾講的鏈接:
=========================================
19.3.4 任務并行庫原理初探
在上一小節中,我們看到只需簡單地調用
Parallel
類中的一些靜態方法,就可以讓代碼并行執行。您一定會對任務并行庫的強大功能有了很深的印象,一些喜歡刨根問底的讀者可能會問:
任務并行庫怎樣實現代碼的并行執行?
任務并行庫的底層技術細節很復雜,要介紹它超出了本書的范疇,然而,對其工作原理作一個介紹是可能的,了解這些知識,對于開發并行程序而言是很有益的。
1 并行指令的生成
軟件工程師使用
Paralllel
類編寫的并行算法,經過編譯器的處理,會全部轉換為對
Task
類相應方法和屬性的調用指令,這些指令被保存到編譯好的程序集中。
Task
類的實例代表一個可以被并行執行的任務,
任務(而不是線程!)是
TPL
實現并行計算的基本單位。
2 任務并行庫的工作原理
任務由線程負責執行,為了獲取較高的性能,
TPL
使用線程池中的線程,并且使用了一個與線程池直接集成的“
任務調度器(
Task Scheduler
)
”來負責分派工作任務給線程,
這個調度器使用的任務分派策略稱為“
Work-stealing
”。
如
圖
19
?
16
所示,線程池中的每個線程都擁有一個專有的(本地的)任務隊列,當線程創建任務(即
Task
類的實例)時,默認設置下,這些任務被放入了線程本地工作隊列中。
如果任務本身是通過調用
ThreadPool.QueueUserWorkItem()
添加的,則此任務會被添加到一個全局隊列(
global queue
)中,這一全局隊列就是
圖
19
?
16
中所示的“線程池任務隊列”。
以下是任務調度器實現任務調度的基本過程:
當任務調度器開始分派任務時,它先檢查一下創建此任務的線程是不是線程池中的線程(這種線程擁有一個本地的任務隊列),如果不是,此任務被加入到線程池全局任務隊列中,如果是,任務調度器檢查此任務是否設置了
TaskCreationOptions.PreferFairness
標記,如果設置了,則此任務被加入到線程池全局任務隊列中,否則,還是被放入到線程的本地隊列中。
當一個線程開始執行時,它優先搜索自己的專有任務隊列,當此隊列為空時,它才會去搜索全局任務隊列。由此可見,這種調度策略實際上是其于優先級的,本地工作隊列比全局隊列擁有更高的優先級。
上述這種默認的調度策略適用于絕大多數情況,但不可能是所有的情況,如果需要對線程本地隊列和線程池全局隊列中的任務一視同仁,在不改變調度策略的情況下(這個策略是由
.NET
為線程池所提供的默認調度器實現的,不可改),可以通過將需要
“
一視同仁
”
的
Task
任務直接放到線程池全局隊列而不是線程本地隊列中實現,其具體的實現方法就是在創建任務時,設置它的
TaskCreationOptions.PreferFairness
標記。
提示:
如果并行執行是通過
Parallel
類的
Invoke
、
For
和
ForEach
方法啟動的,則不能為其指定
TaskCreationOptions.PreferFairness
標記,只有在顯式創建
Task
類的代碼中可以設置此標記。下一小節將介紹如何直接使用
Task
類進行基于“任務”的并行編程。
下面對任務并行庫的工作原理作一個小結。
簡單地說:
線程就是
“
工人
”
,它負責執行
“
任務
”
,任務由任務調度器負責分配。
任務調度器具有很強的智能性,它能自動協調各個任務的分配,不讓
“
忙
”
的線程
“
忙死
”
,
“
閑
”
的線程
“
閑死
”
。從線程的角度看,由于有任務調度器的公平管理,所有線程都是
“
團結互助
”
的
“
雷鋒
”
。
將線程之間合作的工作從線程自身的職責中
“
剝離
”
出來,交由任務調度器來統一協調管理,這是
.NET 4.0
并行計算任務庫設計的一個關鍵點。如果讓線程自身來負責處理工作任務的合理分配,必然會在線程函數內增加同步的代碼,這會讓整個軟件系統變得復雜和難于調試。
我們可以適當地將
TPL
的這種設計思想引申到社會生活領域:如果將線程比喻為
“
政府官員
”
,那么,任務調度器就可以看成是一種
“
制度
”
,正是在
“
制度
”
的制約之下,
“
官員
”
才可能廉潔公正。
在現實社會中,指望貪官他們
“
良心
”
發現而自己
“
金盆洗手
”
是不現實的,必須建立起一種有效的制度,讓所有官員都置于強有力的監督之下,
“
貪污
”
的行為自然會受到極大的制約。這是題外話了。
在下一小節中,我們將開始深入地了解
Task
類。
19.3.5 任務的創建與任務的狀態
1 創建任務
在
19.3.3
節中,我們介紹了使用
Parallel
類的幾個靜態方法(如
Invoke
和
For
)進行并行編程的基本方法,在
19.3.4
節中,我們又知道了實際上
Parallel
類的功能是通過
Task
類實現的,因此,如果我們需要對任務的執行方式有更多的控制,可以直接基于
Task
對象編程而非使用
Parallel
類的靜態方法。
進行并行編程的第一步,是創建一個任務對象。最簡單的方法就是直接使用
new
關鍵字創建
Task
對象。
Task
類的構造函數有多個重載形式,我們逐個介紹其含義和用途:
public Task(Action action);
上述構造函數創建一個
Task
對象,并且讓其關聯一個任務函數(由
action
參數引用),當
Task
對象被線程執行時,此函數被調用。
public Task(Action<object> action, object state);
這一構造函數的第
2
個參數用于向任務函數傳送附加信息,這些附加信息其實就是任務函數調用時的實參。
public Task(Action action, TaskCreationOptions creationOptions);
這一構造函數多了一個
TaskCreationOptions
類型的參數,此參數用于設置任務的屬性標記,上一小節說過,默認情況下新建的任務會放在創建它的線程
[1]
的本地隊列中,如果希望將任務放入線程池的全局隊列中,可以向此構造函數傳入“
TaskCreationOptions.PreferFairness
”值。
[1]
假設此線程是線程池中的線程
public Task(Action<object> action, object state,
TaskCreationOptions creationOptions);
這一構造函數是前
3
個構造函數的“集大成者”,各參數的含義不再贅述。
總結一下,
每個任務一定關聯有一個任務函數
。
這是
Task
對象的本質特征。
創建好以后,并不會自動運行,必須顯示調用它的
Start()
方法。只有此方法被調用之后,此任務才會被插入到線程(或線程池)所關聯的任務隊列中,并在任務調度器的管理下得到執行。
Task t = new Task(() =>
{
…
//
任務函數代碼
});
…
//
任務對象創建完畢,但還未加入到任務隊列中
t.Start(); //
將任務追加到相應的任務隊列中調度執行。
創建任務的第
2
種方法是使用
TaskFactory
類,顧名思義,此類是一個“任務創建工廠”,它提供了“一堆”的公有方法可用于創建任務對象。
Task
類有一個靜態屬性
Factory
可用于引用一個
TaskFactory
對象。
比如,上述創建并啟動一個任務的代碼可以簡化為:
Task t = Task.Factory.StartNew(() =>
{
…
//
任務函數代碼
});
在深入了解
Task
類的基礎之上,
TaskFactory
類的使用就沒有任何奇特之處,請讀者自行查詢
MSDN
了解
TaskFactory
類提供的另外一些方法的用法。
2 了解任務的狀態
“風蕭蕭兮易水寒,壯士一去兮不復還”,與線程對象一樣,每一個
Task
對象都會經歷一個生命周期,在這個生命周期的每個特定階段,對象處于一個特定的狀態,并且不可能由后一個狀態“回轉”到前一個狀態。簡單地說,
Task
對象的生命是一條單行線,一旦上路,就只能往前走,直到生命的終結,期間絕無走回頭路的可能。
如
圖
19
?
17
所示,
Task
對象擁有
8
個狀態,這些狀態之間可以相互轉換。
其中,
Created
是起始狀態,而
Canceled
、
Faulted
和
RanToCompletion
是
3
個終止狀態,其余狀態都是中間狀態。
通過對
Task
類特定的方法的調用,
Task
對象會自動進行狀態的轉換。通常情況下軟件工程師無需考慮這一轉換過程,因為它們是由
TPL
基礎架構直接管理的。
Task
類提供了一個
Status
屬性來表明當前對象所處的狀態,但出于使用方便考慮,
Task
類另外還提供了
3
個相關屬性用于確定對象是否處理
3
個終止狀態之一:
IsCanceled
、
IsFaulted
和
IsCompleted
。
==========================================================
從下一講開始,將介紹在實際開發中針對各種典型開發場景使用Task實現并行計算的基本技術方案。
請看《
.NET 4.0并行計算技術基礎(8
)》
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

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