分享

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

 WindySky 2017-08-01


      在.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对象等,

  另一个有趣的操作是数据库操作及通信操作, 其特征是自身就具有不可控的性能瓶颈,最好通过优化数据库的命令,如连表查询、存储过程等处理。
  但对于懒人,或者不需要再精细优化的情况下,并行任务占据的线程数越少,对整体资源及其他任务的影响也越少,所以我们可以对已经封装的并行化类进行一下最大线程数的限制:

[csharp] view plain copy
  1. class ParaLoop  
  2.  {  
  3.       ....  
  4.      private int _MaxThreadQty;  
  5.   
  6.      private int _CurrentThreadQty;  
  7.      private ManualResetEvent ReqThreadEvent = new ManualResetEvent(false);  
  8.      private object _SynthreadQty = new object();  
  9.   
  10.      public ParaLoop(int mtq)  
  11.      {  
  12.          _MaxThreadQty = mtq;  
  13.      }  
  14.   
  15.      private void ReleaseThread()   //使用完后释放线程,并通知可以申请新线程  
  16.      {  
  17.          lock (_SynthreadQty )  
  18.          {  
  19.              _CurrentThreadQty--;  
  20.              ReqThreadEvent.Set();  
  21.          }  
  22.      }  
  23.   
  24.   
  25.       ~ParaLoop()  
  26.      {  
  27.          ReqThreadEvent.Set();  
  28.      }  
  29.   
  30.      private MyParaThread RequestThread()   // 申请线程,如果达到最大数则等待,直到有新的线程可用  
  31.      {  
  32.          lock (_SynthreadQty)  
  33.          {  
  34.              if (_CurrentThreadQty < _MaxThreadQty)  
  35.              {  
  36.                  _CurrentThreadQty++;  
  37.                  return new MyParaThread();  
  38.              }  
  39.              else  
  40.              {  
  41.                  ReqThreadEvent.Reset();  
  42.              }  
  43.          }  
  44.   
  45.          ReqThreadEvent.WaitOne();  
  46.   
  47.          lock (_SynthreadQty)  
  48.          {  
  49.              _CurrentThreadQty++;  
  50.              return new MyParaThread();  
  51.          }              
  52.      }  
  53.   
  54.     public object ParaCompute(MyParaLoopTaskHandler loopTask, object[] inArg, int loopQty)  
  55.      {  
  56.          ...  
  57.   
  58.          for (int i = 0; i < _TotalQty; i++)  
  59.          {  
  60.              MyParaThread u = RequestThread();  //由直接新建线程改为申请线程  
  61.           //   MyParaThread u = new MyParaThread();  
  62.                
  63.          }  
  64.          _ParaEvent.WaitOne();  
  65.   
  66.          return _ParaLoop_Return;  
  67.      }  
  68.   
  69.   
  70.     void u_EndTaskCallBack(bool taskRst, object retVal)  
  71.      {  
  72.         ...   
  73.   
  74.          ReleaseThread();   //有返回值表示可以释放线程了  
  75.       }  
  76.  }  



  

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多