有关对耗时很大循环进行并行化优化的探讨之三:并行线程越多运行就会越快吗?


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

 

}



 

posted @ 2012-04-11 13:05  窗户纸  阅读(892)  评论(0编辑  收藏  举报