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

