1. 程式人生 > >有關對耗時很大迴圈進行並行化優化的探討之三:並行執行緒越多執行就會越快嗎?

有關對耗時很大迴圈進行並行化優化的探討之三:並行執行緒越多執行就會越快嗎?


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

 

}