在.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 |
從這個有趣的實驗結果可以看出,某些應用下,似乎線程數越多,執行時間反而越慢了, 很多情況下,並行化的程序性能可能反而不如順序執行,甚至會出現一些死鎖等問題,這在微軟的說明中提到了很多(見http://technet.microsoft.com/zh-cn/magazine/dd997392(VS.110).aspx),從這篇文章,我們可以看到,影響性能的因素主要有兩大類:
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(); //有返回值表示可以釋放線程了
}
}
}