有關對耗時很大循環進行並行化優化的探討之三:並行線程越多運行就會越快嗎?


      在.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();   //有返回值表示可以釋放線程了
         }
    }

 

}



 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章