CIO
|
PCDIY!
|
DigitalHome
|
旗標圖書
|
旗景數位影像
|
旗標知識講堂
|
讀者服務
首 頁
即時新聞
最新活動
企業採購
精選文章
線上教學
雜誌目錄
程式碼下載
雲端運算智庫
Visual Studio 2010
Windows 7 FAQ
最近新增的文章
Zoner Photo Studio 12試用報導
EMC VPLEX跨IDC儲存新架構
Clavister Security Gateway高效能網路安全閘道
Lenovo ThinkCentre A70z
更成熟穩健的 Web Forms 4.0
建立開發團隊專屬的最佳Scrum流程
打造全新軟體測試與驗證中心
Visual Studio Team Explorer Everywhere整合帶來的優勢
輕鬆建構軟體藍圖
軟體生命週期的版本與流程控管
大幅降低平行運算開發的門檻
邁向雲端之路
前端介面之使用者經驗設計趨勢 ─ Silverlight 4.0
新一代整合式開發平台導覽
把Dynamic Data整合到Web Forms
最多人點閱的文章
ASP.NET MVC 1.0初探
MSN機器人開發(上)
Lynnfield上市 新款i5/i7效能比較
初探Hadoop開放原始碼平台環境
MSN機器人開發(下)
抗摔防潑水商用NB解密
Acer DX900 WM雙模雙待機
虛擬化環境下 資料備份與管理
Windows Server 基礎架構與管理的改善
Anti-rootkit工具介紹
ASP.NET AJAX Templates初探
虛擬化環境下資料備份與管理Part 2
惡意程式的隱形斗蓬-rootkit
ASP.NET AJAX Templates整合應用
耐用度指標商務機種—HP Elitebook
精選文章
分享到Plurk
分享到FaceBook
.NET執行緒控制技巧
運用ThreadPool發揮CPU運算能力
文/圖 吳剛志.責任編輯/洪羿漣
雙核心CPU已經不是新技術了,你還在寫單一執行緒(Thread)的程式嗎?是否常覺得你的程式跑的不夠快,但是CPU卻是閒在一旁沒有事情作?沒有多核心的系統,多執行緒就沒有用了嗎?本文將以簡單的範例,帶領讀者利用執行緒集區(ThreadPool),有效的運用你的CPU運算能力,加速你的程式。
寫了2期在多執行緒環境下寫程式的技巧(如ASP.NET),這次改變角色,直接來探討該如何建立個有效率的多執行緒程式。
在這個多核心處理器已經是主流的年代,已經不需要再去宣揚多處理器的好處了。現在面臨到的課題反而是:我的程式已經能應用到多執行緒了嗎?越多的核心能夠讓我的程式跑的更快嗎?
問題的答案很簡單,如果你要執行的程式特性是大量且獨立的工作,只要善用多執行緒就可以辦的到。不過要實作也不簡單,因為控制、建立及中止執行緒,甚至是效能的調校(多少執行緒才有最佳效能?),往往是初次進入這領域的軟體工程師最頭痛的部份,常常想到腦袋打結。
這部份難道沒有簡單一點的作法嗎?當然有,就是用本篇的主題:執行緒集區(ThreadPool)。
執行緒集區是一組能自行管理及重複使用執行緒來幫你完成工作的函式庫,它會幫你處理掉所有執行緒的管理工作,你只管把工作交給它,然後等著工作完成就好。
執行緒集區用起來並不難,不過沒寫過多執行緒程式的話,是很難體會出其好處到底在那裡。
執行緒範例比較
本篇文章會先從簡單的範例程式,來看看自己處理執行緒,跟利用執行緒集區的效能差異有多大。所有範例用的例子都是在處理同一個問題,那就是將硬碟裡某個目錄的圖檔(約350個千萬像素的JPEG檔),每張都轉成兩百萬像素(1600 x 1200)大小的圖檔。
範例一:不使用多執行緒
「這有什麼難的?」有經驗的讀者一定會這樣想。想完的同時也順手把程式寫完了。
我們不去討論圖檔應該怎麼處理等等的細節,重點會擺在怎麼妥善的安排執行緒,讓四核心的系統能夠用最有效率的方式完成這批工作。若不考慮執行緒的問題,一個foreach迴圈加上處理縮圖的Method就搞定了(程式1)。
程式1
程式很快就寫完了,不過執行的效能卻令人不滿意。在配備四核心CPU的機器上執行看看,CPU使用率只有30%不到,這樣的表現,意思是你多買的三個核心是浪費掉的。
先說明一下測試程式執行的環境。本篇所有程式都是在Vista SP1 (x64) + .NET Framework 2.0環境下測試,而PC的硬體規格為Intel Q9300 (4核心) + 8GB RAM。
來看看第一個範例程式執行的輸出結果,如表1,執行時工作管理員顯示的負載狀態如圖1。
表1:程式1執行結果統計
圖1:範例一執行時的CPU及MEMORY使用情況。
範例二:每個檔案用一個執行緒
你滿意範例一這樣的結果嗎?CPU明明還有3/4的運算能力還沒用掉。接下來我們來看一下第二個範例程式,稍微改寫一下,讓每個檔案都有一個專用的執行緒來處理,處理完畢執行緒就中止了,而整個主程式則在所有執行緒都中止之後就算完成。
來看看程式2的範例,以及它的執行結果,如表2、圖2。
程式2
表2:程式2的執行結果統計
圖2:程式2執行時的CPU及MEMORY使用情況。
程式2範例執行起來,對電腦真是嚴格的考驗。同一瞬間開一堆執行緒處理所有的圖檔。在程式執行過程中電腦幾乎都不能動,記憶體使用量也飆升到8GB。不過整體的執行效果還是比範例一來的好,以所有的檔案處理完成的時間來看,執行效率是程式1的153%。
不過這樣的寫法實在是不鼓勵,原本在32位元版本的XP上面跟本跑不完,一直出現OutOfMemoryException。不過轉幾張圖檔要動用到64位元作業系統,還要備足8GB的RAM,這種程式沒有幾個讀者能接受吧?
範例三:固定執行緒個數(4個)
再來採取第三種方案,為了避開範例二過多執行緒的問題,這次我們改用固定個數的執行緒。不過該如何把工作分給4個執行緒?想像一下排隊買票的狀況。
4個售票櫃台,有上百人等著排隊買票,每個櫃台服務完客戶後就接著服務下一位,直到票賣完為止,或是所有排隊的人都買到票了。
要把這樣的邏輯應用在程式上,我們用QUEUE物件來實作,而每個執行緒處理完目前的工作後就從QUEUE去領取下一個工作來作,直到QUEUE空了為止。
聽起來聰明多了,來看看程式3的程式碼。
程式3
程式碼除了從QUEUE取出物件的地方要注意之外,其它就沒有什麼特別的了。寫多執行緒程式一定要注意的就是共用物件是不是「Thread Safe」,這裡使用的QUEUE物件,在微軟提供的 .NET Framework SDK裡有這麼一段說明:
Public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.
A Queue<(Of <(T>)>) can support multiple readers concurrently, as long as the collection is not modified. Even so, enumerating through a collection is intrinsically not a thread-safe procedure. To guarantee thread safety during enumeration, you can lock the collection during the entire enumeration. To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization.
多個執行緒在同一瞬間從QUEUE取出物件時,有可能會造成QUEUE的運作不正確,因此我們必需用鎖定的機制來確保任何時間內,只有一個執行緒能進入這個區段。
接著看看執行的效果,如表3、圖3。
表3:程式3的執行結果統計
圖3:程式3執行時的CPU及MEMORY使用情況。
這次的效果又更好了,CPU使用率平穩的維持在80%左右,記憶體使用量也很平穩,沒有爆衝的情況。執行效率是範例一的238%。
不過CPU的使用率仍然還有1/4沒發揮出來,也許程式還有改善的空間。大部份的情況下,系統不會只有CPU在忙,I/O、MEMORY等等也有可能是瓶頸。
舉例來說,當檔案正在透過網路或是硬碟讀取時,CPU就會閒置,直到它需要的資料準備好才開始動作。這就是為什麼4個執行緒不一定能讓4個核心完全保持忙碌。
範例4:固定執行緒個數(8個)
接下來調整一下範例三,程式架構不變,唯一改變的是把4個執行緒改成8個,再測試一次,結果請參考表4、圖4。
表4:程式4的執行結果統計
圖4:程式4執行時的CPU及MEMORY使用情況。
這次的結果又比上一次更好一些,CPU使用率都停在90% ~ 95%跳動,整體執行效率是範例一的343%。
效能調校
到了這裡,我們暫停修改程式,先來分析前面幾個例子的執行結果,思考一下有什麼可行的方法能改善效能。
既然CPU還有多餘的運算能力,那麼適當的再多加幾個執行緒,就有機會把剩餘的運算能力用掉 (不過當然不能像第二個例子這麼極端)。不過以這幾個例子來看,困難的不在寫程式,而是像範例二及範例三這樣的效能調校。寫程式時你怎麼知道要用幾個執行緒?難道每寫一個程式都得這樣調校一次嗎?每種不同的執行環境都得調校一次嗎?
我們需要更聰明的執行緒運用方式。從範例一及範例二得知,過多或過少的執行緒,對系統效能是沒有幫助的,而範例三及範例四告訴我們,找到適當的執行緒數量,才能讓系統整體效能極大化。接下來的問題是,怎樣讓程式自動的調整到最佳狀態?
為了不過度的建立及中止執行緒,我們需要應用到「集區(POOL)」的概念,來重複使用執行緒。而面對效能問題,當CPU還有多餘運算能力,我們可以逐漸增加執行緒的數量,直到系統效能飽合為止,當系統負核過重時(如範例二),要反過來減少執行緒的數量,讓系統整體效能隨時都維持在最佳狀態。
講到這裡讀者們大概就都暈頭了。對於執行緒的程式我們也不會再這篇文章繼續挖深下去,Microsoft已經在 .NET Framework提供了一個完整的ThreadPool類別。內建的ThreadPool在效能方面調校的很不錯,大部份的情況下,運用內建的ThreadPool的效能都不會比自行控制執行緒差。
範例五:使用內建的ThreadPool
真的這麼神奇嗎?第五個範例就直接用ThreadPool改寫。寫起來很簡單,只要把要執行的工作以DELEGATE的方式交給ThreadPool,ThreadPool就會自行安排執行緒來處理你的工作。程式碼變的相當簡潔,先來看看程式4的程式碼。
程式4
把工作放到ThreadPool很簡單,只要把工作包裝好丟進去就可以了。包裝要被執行的工作,用的是名為WaitCallBack的delegate物件,把這個物件當作參數,傳給ThreadPool.QueueUserWorkItem(callback, state)就可以了。
不過這樣一來工作開始執行了,卻不知道何時會結束,或是結束了沒。狀態的傳遞可以透過WaitCallBack的參數state來完成。除此之外,我們還需要一個機制來通知主程式,得知排入的工作是否已經執行完成了。
這個部份可以透過ManualResetEvent來進行。關於這個物件我們在上一篇文章(第171期)已經介紹過,在工作開始執行前先準備好ManualResetEvent物件,待工作完成後呼叫ManualResetEvent.Set( ),主執行緒就可以藉著呼叫WaitOne()就能知道工作到底執行完成了沒。
這範例程式也很簡潔,直接來看看執行的效果,如表5、圖5。
表5:範例五的執行結果統計
圖5:範例五執行時的CPU及MEMORY使用情況。
結果不錯,跟範例四差不多,CPU負載大約是 95%左右,執行效率是範例一的335%。
效能分析
到目前為止,執行效能是範例四及範例五最好,其它則遠遠落後。比較一下範例四跟範例五的程式碼的差別,範例四我們需要經過效能調校的動作,才能知道要用多少執行緒才有最佳效能,而範例五則不需要這樣的調校過程,只要照著文件來使用執行緒集區,就可以有一樣的效能表現。
不過事情還沒結束,在你的機器上調好效能,換到其它機器上也能有一樣的結果嗎?這時執行緒集區的優點就突顯出來了,我們不需要作調校的動作,執行緒集區會自己調整到最佳狀態。 我們再進一步來看看執行緒集區還有什麼潛力。前面提到程式在執行有很多機會會讓CPU停下來等待(像是網路傳輸、磁碟I/O,甚至是使用者的輸入動作等),這次我們在轉檔的動作裡插入Thread.Sleep(),讓CPU轉完檔案後會暫時發呆一下,來模擬實際運作時可能會碰到讓CPU閒置的情況。加上這段修改後,來看看前面五個例子的執行結果各會有什麼變化,請參考圖6。
圖6:CPU閒置時間,與程式總執行時間的關係圖。
喔!傑克,這真是太神奇了。插入IDLE時間,會讓每一個轉檔的動作花更多時間才能完成,不過範例五的總執行時間卻沒有被影響到,仍然在一樣的時間內完成所有的工作。
隨著插入的IDLE時間不斷的增加,除了範例五的曲線幾乎維持水平的之外,其它的範例都會隨著IDLE時間的增加,需要更多的時間完成工作。範例二是個例外,因為過多的執行緒讓效能低落,這時越多的IDLE時間反而有助無減緩這局勢,因此IDLE時間越多它越接近「正常」的效能。
原理很簡單,一切都是因為執行緒集區能自動視系統的負載情況,調整執行緒的數量才有這樣的效果。額外加入的IDLE指令,空出來的CPU運算能力不會被浪費掉,執行緒集區會聰明的再多加一些執行緒進來工作,善加利用多餘的運算能力。也因此在總工作量不變的情況下,仍夠能在一樣的時間內完成所有的工作。
這樣的能力對使用者有什麼實際的效益?如果CPU閒置時間是因為網路傳輸所造成的,那這代表善用執行緒集區的話,就能夠讓這類網路傳輸造成的延遲影響降到最低。即使要轉檔的檔案放在網路磁碟機,也有機會在同樣的時間完成同樣的工作。
圖7:CPU閒置時間,與各範例程式效能提升百分比關係圖。
換個角度來看一下圖7統計數字,這次把執行的時間,換成同樣環境下,以範例一的效能為100%,推算出其它範例程式的相對效能百分比。使用執行緒集區(範例五)的效能直線攀升,達到了十一倍的效能提升!已超過原本預期4核心的系統最多只會有4倍效能提升的假設。
結語
這個範例的統計圖表,也證明了另一件常被大家誤解的事實:多執行緒的作法只對多處理器的系統有幫助。讀者們如果把這範例拿到單核心的系統上測試,會發現使用執行緒一樣會有效能的增進,只不過幅度當然不會像4核心這樣的明顯。(完整程式碼請至www.runpc.com.tw下載)
【原文刊載於RUN!PC雜誌:2008年9月號】
回首頁...
關於RUN!PC
|
廣告刊登
|
聯絡我們
|
讀者服務
|
雜誌訂閱
|
出刊&補寄時間
|
招兵買馬
-- Copyright© FLAG INFORMATION CO., LTD. 旗訊科技(股)公司. All rights reserved. 本站圖文著作權所有 未經授權 不得任意轉載使用 --
-- 請使用1024*768螢幕解析度,IE 7.0或firefox 3.0以上瀏覽器,以達到最佳閱讀效果--