专注于互联网--专注于架构

最新标签
网站地图
文章索引
Rss订阅

首页 »DotNet » 线程池原理:CLR线程池的作用和原理浅析 »正文

线程池原理:CLR线程池的作用和原理浅析

来源: 发布时间:星期三, 2009年9月2日 浏览:169次 评论:0
  线程池是个重要概念不过我发现有关这个话题讨论似乎还缺少了点什么作为资料补充以及今后文章所需要引用我在这里再完整而又简单地谈下有关线程池还有.NET中各种线程池基础更详细内容就不多作展开了有机会我们再详细讨论这方面细节这次还是个“概述”性质希望可以介绍说明白这方面问题些概念

  线程池作用

  其实“线程池”就是用来存放“线程”对象池

  在如果某个创建某种对象所需要代价太高同时这个对象又可以反复使用那么我们往往就会准备个容器用来保存批这样对象于是乎我们想要用这种对象时就不需要每次去创建而直接从容器中取出个现成对象就可以了由于节省了创建对象开销性能自然就上升了这个容器就是“池”很容易理解有了对象池因此在用完对象的后必须有个“归还”动作这样便可以把对象放回池中下次需要时候就可以再次拿出来使用了

  例如我们在使用ADO.NET连接SQL Server时.NET框架就会自动帮我们维护个连接池这就是重新创建个连接代价相对比较高昂“复用”就显得比较划算了不过有些朋友可能会说我们明明是每次都创建个SqlConnection对象哪里有“复用”啊?这是.NET框架中把“连接池”做透明了对于员完全隐藏了这个概念每次我们虽然创建是新SqlConnection对象但是这个对象内部占用“数据库连接”还是会复用为什么总是强完SqlConnection对象后要及时“关闭”(Dispose或Close)呢?其实这里并没有断开数据库连接只是把这个连接放回了连接池等到下次创建新SqlConnection对象时这个连接又可以拿出来用了

  既然我们每次都是从池中获取对象那么这些对象是由谁来创建又是什么时候创建呢?这个就要根据区别情况由各对象池来自行实现了例如可以在创建对象池时候指定池内对象数量并且下子全部创建好当然您也可以在得到请求时如果发现池中已经没有剩余对象时创建您也可以“事前”先准备部分“事中”根据需要再继续补充还可以做得“智能”例如根据实际情况添加或删除些对象甚至对需求“走势”进行“预测”在空闲时便创建更多对象以备“不时的需”各中变化难以言尽

  当然它们原理和目是类似相信上面这段文字也已经讲清了“线程池”作用:创建个线程代价较高因此我们使用线程池设法复用线程就是这么简单

  CLR线程池作用

  在.NET中CLR线程和操作系统线程对应您可以简单地认为.NET中Thread对象便封装了个操作系统线程并附带些托管环境下所需要数据(如GC Handle)1而CLR线程池便是存放这些CLR线程对象池

  我们在编写时候可以使用ThreadPool类两个静态思路方法:QueueUserWorkItem和UnsafeUserQueueWorkItem向CLR线程池中添加任务(个WorkCallback委托对象)这两个思路方法区别在于前者会收集ExecutionContext也就是保留了当前线程执行信息(如认证或语言文化等)使任务最终会在“创建”时刻环境中执行2——后者就不会因此如果比较两个思路方法绝对性能Unsafe思路方法会略胜但是平时还是建议使用QueueUserWorkItem思路方法保留执行上下文会避免很多麻烦事情且这点性能损耗其实算不上什么

  CLR线程池在.NET框架中作用很大除了让员使用的外其他些功能也会依赖CLR线程池如ThreadPool.RegisterWaitForSingleObject思路方法或是.Threading.Timer组件——还有更重要可能也是更隐藏:ASP.NET在得到个请求后也会将这个请求处理任务交由CLR线程池去执行——请注意它们最多只是添加任务而已并不表示任务会立即执行所有添加到CLR线程池任务都会在合适时候得以执行——可能马上也可能要稍等片刻甚至更久

  向CLR线程池添加任务时任务会被临时放到个队列中并在合适时候执行那么如何样才算是“合适时候”?简单概括说来便是线程池内有空闲线程或线程池所管理线程数量还没有达到上限时候如果有空闲线程线程池就会立即让它领取个任务执行如果是第 2种情况线程池便会创建新Thread对象由于让操作系统管理太多线程反而会造成性能下降因此CLR线程池会有个上限区别托管环境会设置区别上限如在.NET 2.0 SP1的后普通Windows应用(如控制台或WinForm/WPF)会将其设置为“处理器数 * 250”也就是说如果您机器为2个2核CPU那么CLR线程池容量默认上限便是1000也就是说它最多可以管理1000个线程同时运行——很多情况下这已经是个很可怕数字了如果您觉得这还不够那么就应该考虑下您实现方式是否可以改进了

  对于ASP.NET应用来说CLR线程池容量代表了应用最多可以同时执行请求数量对于托管在IIS上ASP.NET执行环境来说这个值由全局配置决定这个配置在machine.config文件中system.web/processModel节点中为maxWorkerThreads属性它决定了为单个处理器分配线程数如果这个值为40且机器上拥有4个处理器(2 * 2CPU)那么这台机器目前配置表示在同时刻ASP.NET可以同时处理160个请求某些参考资料建议您将其修改为每处理器80-100个线程这时您只要修改相应属性值就可以了

  既然有最大值也就相应有了最小值它代表了CLR线程池“总是会保留”最少线程数量由于线程会占用资源如在默认情况下每个线程将获得1MB大小栈空间3所以如果在系统中保留太多空闲线程对资源也是种浪费因此CLR线程池在使用大量线程处理完大量任务的后也会逐步地释放线程直至到达最小值CLR线程池最小线程数量确保了在任务数量较少情况下新来任务可以立即执行从而省去了创建新线程时间在普通应用中这个值为“处理器数 * 1”而在ASP.NET应用中这个值配置在machine.config文件中system.web/processModel节点minWorkerThreads属性中4

  在某些时候可能会遇到这样情况:在个瞬间忽然来大量任务每个任务执行时间说长不长说短不短不过足以导致线程池快速分配数百个线程如果这个峰值的后就片平静那么势必造成大量空闲线程这种开销对性能损耗也非常明显因此CLR线程池限制了线程创建速度不超过每秒2个这样即使在某个瞬时获得了大量任务CLR线程池也可以使用相对较少线程来完成所有工作5

  但是还有种情况也值得考虑例如对于个比较繁忙Web应用来说打开便会涌入大量连接由于线程创建速度有限因此可以执行请求数量也只能慢慢增加对于这种您预料到会产生大量线程而且忙碌状况会持续段时间情况限制线程创建速度反而会带来损伤效率这时您就可以手动设置CLR线程池最小线程数量如果此时CLR线程池中拥有线程数量较少那么系统就会立即创建定数量线程来达到这个最小值设置和获取CLR线程池最小线程数量接口为:

public   ThreadPool  
{  
    public  void GetMinThreads(out  workerThreads, out  completionPortThreads);  
    public  bool SetMinThreads( workerThreads,  completionPortThreads);  
} 


  这两个接口作用和使用方式应该足够明显了(不理解话可以查阅MSDN)其中workerThreads参数便是CLR线程池最小线程数而completionPortThreads涉及到我们下次要讨论IO线程池在此就不多作展开了除了设置和读取CLR最小线程数思路方法的外ThreadPool还包含这些接口:

public   ThreadPool  
{  
    public  void GetMaxThreads(out  workerThreads, out  completionPortThreads);  
    public  bool SetMaxThreads( workerThreads,  completionPortThreads);  
    public  void GetAvailableThreads(out  workerThreads, out  completionPortThreads);  
} 


  值得注意无论是设置还是获取到这些数值都和处理器数量没有任何关系了也就是说台2 * 2CPU机器上运行个普通.NET应用时:

  GetMaxThreads思路方法将获得1000表示CLR线程池最大容量为1000(250 * 4)而不是250

  SetMinThreads并传入100表示CLR线程池所拥有最小线程数量为100而不是400(100 * 4)

  对于CLR线程池作用简单描述就暂时先到这里了如果您还有什么疑问请提出我会加以补充

  注1:严格说来Thread对象和系统线程对应关系还有些细节上考虑例如Thread对象只有当真正Start了的后CLR才会创建个操作系统线程和它绑定

  注2:ExecutionContext是个很重要且很有用对象例如WinForms或WPF异步任务中操作界面元素抛出异常该如何办呢?

  注3:使用Windows API或Thread类创建线程时可以指定它栈空间大小但是CLR线程池中线程只能使用默认值——不过这个默认值也和托管环境有关如普通应用默认为1MB而ASP.NET为250KB这意味着ASP.NET应用相对更容易产生Stack Overflow异常



  注4:可惜对于processModel节点数据ASP.NET只会读取machine.config中全局配置信息这意味着我们不能使用web.config为区别应用配置区别参数如果我们要实现应用级别配置那么必须使用ThreadPool类中提供API进行设置这点稍后便会提到

  注5:对于这点您不妨来做个算术题:线程池内下子涌入了500个任务每个任务阻塞或暂停5秒每个线程占用1MB内存假设线程池目前为空且有着足够容量此外线程创建速度也足够快那么在限制及不限制线程创建速度情况下完成这些任务需要多少时间和内存空间?



0

相关文章

读者评论

发表评论

  • 昵称:
  • 内容: