有關對耗時很大迴圈進行並行化優化的探討之三:並行執行緒越多執行就會越快嗎?
在.net framework4.0以後,出現了並行程式設計的概念,使用 Parallel.For(0, N, i =>{ ... },很容易就可以實現我們自定義的並行化迴圈操作,在並行迴圈體中,還可以操作外部的變數,這個特性是很多其他語言所沒有的,當然其他語言,諸如JAVA之類,完全可以採用我們在第二篇所介紹的方法自己生成並行化操作。
由於.net framework使用環境的侷限性,以及“龐大的身軀”,和安裝時必須向微軟“報到”的限制。很多開發商並不喜歡用,完全可以採用我所介紹的方式實現並行化,與微軟的並行化的區別只是他的迴圈體是使用了lamda表示式,而我的方式是使用委託而已。再發散一下,我個人認為微軟的並行化操作在並行化優化方面,多處理器利用方面會更有優勢些,效能會更好。
但.Net FrameWork中對並行化並行化的最大執行緒數貌似並沒有進行個性化限定,執行起來自然是並行任務能開多少開多少了。就並行執行緒數的問題,我們在某個專案中做了一個實驗,具體的並行任務中既有資料庫操作,又有通訊操作,還包括了若干鎖定資源操作,以下就是實驗結果:
最大執行緒數 |
程式平均執行時間 |
單執行緒 |
31470 ms |
15執行緒 |
20042 ms |
5執行緒 |
20307 ms |
3執行緒 |
18745 ms |
2執行緒 |
18523 ms |
從這個有趣的實驗結果可以看出,某些應用下,似乎執行緒數越多,執行時間反而越慢了, 很多情況下,並行化的程式效能可能反而不如順序執行,甚至會出現一些死鎖等問題,這在微軟的說明中提到了很多(見
1. 對共享資源的鎖定問題:這個問題比較好理解,如果多個並行任務嘗試操作被鎖定的記憶體位置,那麼後來的肯定要等待,對於考慮不很周到的程式碼,其結果就是比序列機制還慢.
2. 迴圈體內的物件執行機制問題
在微軟的說明中,提到了“不安全的物件”並行化的問題,比如filestream, ui物件等,
另一個有趣的操作是資料庫操作及通訊操作, 其特徵是自身就具有不可控的效能瓶頸,最好通過優化資料庫的命令,如連表查詢、儲存過程等處理。
但對於懶人,或者不需要再精細優化的情況下,並行任務佔據的執行緒數越少,對整體資源及其他任務的影響也越少,所以我們可以對已經封裝的並行化類進行一下最大執行緒數的限制:
class ParaLoop
{
....
private int _MaxThreadQty;
private int _CurrentThreadQty;
private ManualResetEvent ReqThreadEvent = new ManualResetEvent(false);
private object _SynthreadQty = new object();
public ParaLoop(int mtq)
{
_MaxThreadQty = mtq;
}
private void ReleaseThread() //使用完後釋放執行緒,並通知可以申請新執行緒
{
lock (_SynthreadQty )
{
_CurrentThreadQty--;
ReqThreadEvent.Set();
}
}
~ParaLoop()
{
ReqThreadEvent.Set();
}
private MyParaThread RequestThread() // 申請執行緒,如果達到最大數則等待,直到有新的執行緒可用
{
lock (_SynthreadQty)
{
if (_CurrentThreadQty < _MaxThreadQty)
{
_CurrentThreadQty++;
return new MyParaThread();
}
else
{
ReqThreadEvent.Reset();
}
}
ReqThreadEvent.WaitOne();
lock (_SynthreadQty)
{
_CurrentThreadQty++;
return new MyParaThread();
}
}
public object ParaCompute(MyParaLoopTaskHandler loopTask, object[] inArg, int loopQty)
{
...
for (int i = 0; i < _TotalQty; i++)
{
MyParaThread u = RequestThread(); //由直接新建執行緒改為申請執行緒
// MyParaThread u = new MyParaThread();
}
_ParaEvent.WaitOne();
return _ParaLoop_Return;
}
void u_EndTaskCallBack(bool taskRst, object retVal)
{
...
ReleaseThread(); //有返回值表示可以釋放執行緒了
}
}
}