[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
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET