[C#.NET][Thread] 執行緒效能比較 - Parallel / Thread / ThreadPool
在開始之前我們先來複習一下原本Thread類別以及TreadPool的用法,由主執行緒呼叫其他執行緒進行運算,運算結果會存放至一個全域變數,然後由主執行緒印出執行緒運算出來的結果。
宣告變數
public static int _ResultNumber = 0; public static int _ActualNumber = 0; static List<Thread> _Threads = null; static object _lock = new object(); static int _LoopCount = 10000; static AutoResetEvent[] _AutoEvents = new AutoResetEvent[_LoopCount]; static ManualResetEvent _ManualEvent = new ManualResetEvent(false); static void Main(string[] args) { //UseThreadClass(); UseThreadPoolClass(); // UseParallelClass(); }
PublicMethod只是用來驗証執行緒所演算的結果是否正確
static void PublicMethod() { Console.WriteLine("Multi Thread calculate Result value = {0}", _ResultNumber); int result = 0; for (int i = 0; i < _LoopCount; i++) result += i; _ActualNumber = result; Console.WriteLine("Single Thread calculate Result value = {0}", _ActualNumber); Console.WriteLine("main thread exists.."); Console.Read(); }
Thread類別用法:
使用若要主執行緒等待其他執行緒完成,可以使用Thread.Join方法,來確保所有執行緒已完成工作。
static void UseThreadClass() { _ResultNumber = 0; _ActualNumber = 0; _Threads = new List<Thread>(); for (int i = 0; i < _LoopCount; i++) { Thread t = new Thread(new ParameterizedThreadStart(ThreadDoWorker)); _Threads.Add(t); t.Start(i); } foreach (Thread item in _Threads) item.Join(); PublicMethod(); }
Doworker是執行緒的共用資源,為確保共用資源一次只有一條執行緒會進入,所以要把它用lock或是其他鎖定類別鎖起來,若沒鎖起來_ResultNumber計算出來的結果每次都會不一樣。
static void ThreadDoWorker(object i) { lock (_lock) { Thread t = Thread.CurrentThread; _ResultNumber += (int)i; Console.WriteLine("No.{0}-Thread[{1}]:{2},Value is {3}", i, t.ManagedThreadId, t.ThreadState, _ResultNumber); } }
Multi Thread計算結果與Single Thread計算結果一樣,表示執行緒的演算正確,只要沒有Join或是lock,主執行緒取得的結果就會不一樣,所以使用Thread要注意的事情比較多一點。
ThreadPool的用法:
ThreadPool因為沒有Join,所以必須要用WaitHandle來鎖定以及等待,在此範例我採用AutoResetEvent來處理,本來是想 用WaitHandle.WaitAll方法,但是這個方法只能容許64個陣列索引,所以才改用AutoResetEvent來等待。有關 WaitHandle.WaitAll方法等待用法可以參考http://msdn.microsoft.com/zh-tw/library/system.threading.manualresetevent.aspx
static void UseThreadPoolClass() { _ResultNumber = 0; _ActualNumber = 0; ThreadPool.SetMaxThreads(10, 10); for (int i = 0; i < _LoopCount; i++) { ThreadPool.QueueUserWorkItem(new WaitCallback(PoolDoWorker), i); _AutoEvents[i] = new AutoResetEvent(false); } for (int i = 0; i < _AutoEvents.Length; i++) { _AutoEvents[i].Set(); Thread.Sleep(1); } //_ManualEvent.Set(); //WaitHandle.WaitAll(_AutoEvents); PublicMethod(); }
用WaitOne方法來鎖定
static void PoolDoWorker(object i) { //_ManualEvent.WaitOne(); _AutoEvents[(int)i].WaitOne(); Thread t = Thread.CurrentThread; _ResultNumber += (int)i; Console.WriteLine("No.{0}-Thread[{1}]:{2},Value is {3}", i, t.ManagedThreadId, t.ThreadState, _ResultNumber); //_AutoEvents[(int)i].Set(); }
ThreadPool的比Thread的還要好,ThreadPool只用了Thread[13]~[16]條執行緒在處理工作
Parallel 的用法:
在.Net 4.0裡所新增的Parallel 類別,不用鎖定不用等待,主執行緒就能取得正確的共用資源了,自動已經幫你做好處理同步機制了,真是佛心來的新功能~
static void UseParallelClass() { _ResultNumber = 0; _ActualNumber = 0; Parallel.For(0, _LoopCount, ParallelDoWorker); PublicMethod(); }
執行緒共用資源裡也不用鎖定,它取代原本的Thread寫法, 看起就變的更精簡了。
static void ParallelDoWorker(int i) { Thread t = Thread.CurrentThread; _ResultNumber += (int)i; Console.WriteLine("No.{0}-Thread[{1}]:{2},Value is {3}", i, t.ManagedThreadId, t.ThreadState, _ResultNumber); //Thread.Sleep(1); }
PS.我在ParallelDoWorker方法裡用了Sleep方法,計算出來的結果會錯誤!
執行緒的效能也似乎比傳統呼叫Thread類別來的省資源,只用了Thread[9]~[13]處理工作。
接下來用單元測試來觀察哪一個用法的效能比較優,我在UseThreadPoolClass方法裡有用了Sleep(1),所以ThreadPool自然會輸給件Parallel,若是將測試數量減少Parallel仍略勝一籌
CPU使用情況
Thread類別
ThreadPool類別
Parallel類別
延伸閱讀:
http://www.dotblogs.com.tw/code6421/archive/2010/02/25/13766.aspx
http://www.dotblogs.com.tw/code6421/archive/2010/02/25/13767.aspx
http://www.dotblogs.com.tw/code6421/archive/2010/02/25/13768.aspx
http://www.dotblogs.com.tw/code6421/archive/2010/02/25/13769.aspx
http://www.dotblogs.com.tw/johnny/archive/2010/12/18/20225.aspx
http://blog.darkthread.net/blogs/darkthreadtw/archive/2010/01/22/multicore-3.aspx
本文转自http://www.dotblogs.com.tw/yc421206/archive/2011/01/15/20827.aspx