专栏首页明丰随笔线程池的作用和CLR线程池

线程池的作用和CLR线程池

1.线程池的作用

【线程池】就是用来存放【线程】的对象池。

在程序的世界里,如果创建某种对象所需要的代价太高,同时这个对象又可以反复使用,那么我们往往就会准备一个容器,用来保存一批这样的对象。当我们要用这种对象时,就不需要每次去创建一个,而是直接从容器中取出一个现成的对象。由于节省了创建对象的开销,程序性能自然就上升了。这个容器就是“池”。很容易理解的是,因为有了对象池,在用完对象之后应该有一个“归还”的动作,这样便可以把对象放回池中,下次需要的时候就可以再次拿出来使用。既然我们每次都是从池中获取对象,那么这些对象是由谁来创建,又是什么时候创建的呢?这个就要根据不同情况由各对象池来自行实现了。例如,可以在创建对象池的时候指定池内对象数量,并且一下子全部创建好,当然您也可以在得到请求时,如果发现池中已经没有剩余对象时创建。您也可以“事前”先准备一部分,“事中”根据需要再继续补充。还可以做得“智能”一些,例如,根据实际情况添加或删除一些对象,甚至对需求“走势”进行“预测”,在空闲时便创建更多的对象以备“不时之需”。各中变化难以言尽。当然,它们的原理和目的是类似的。相信上面这段文字也已经讲清了“线程池”的作用:因为创建一个线程的代价较高,因此我们使用线程池设法复用线程。就是这么简单。

2.数据库的连接池

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

3.CLR线程池

在.NET中,CLR线程和操作系统线程对应,您可以简单地认为.NET中的Thread对象Start了之后便封装了一个操作系统线程,并附带一些托管环境下所需要的数据(如GCHandle)。而CLR线程池便是存放这些CLR线程的对象池。ASP.NET在得到一个请求后,也会将这个请求处理的任务交由CLR线程池去执行——请注意,它们最多只是添加任务而已,并不表示任务会立即执行。所有添加到CLR线程池的任务都会在合适的时候得以执行——可能马上,也可能要稍等片刻,甚至更久。向CLR线程池添加任务时,任务会被临时放到一个队列中,并在合适的时候执行。那么怎么样才算是“合适的时候”?简单的概括说来,便是线程池内有空闲的线程,或线程池所管理的线程数量还没有达到上限的时候。如果有空闲的线程,线程池就会立即让它领取一个任务执行。如果是第二种情况,线程池便会创建新的Thread对象。由于让操作系统管理太多线程反而会造成性能下降,因此CLR线程池会有一个上限。不同的托管环境会设置不同的上限。对于ASP.NET应用程序来说,CLR线程池容量代表了应用程序最多可以同时执行的请求数量。对于托管在IIS上的ASP.NET执行环境来说,这个值由全局配置决定。这个配置在machine.config文件中system.web/processModel节点中,为maxWorkerThreads属性,它决定了为单个处理器分配的线程数。如果这个值为40,且机器上拥有4个处理器(2*2CPU),那么这台机器目前的配置表示在同一时刻,ASP.NET可以同时处理160个请求。某些参考资料建议您将其修改为每处理器80-100个线程,这时您只要修改相应的属性值就可以了。既然有最大值,也就相应有了最小值,它代表了CLR线程池“总是会保留”的最少线程数量。由于线程会占用资源,如在默认情况下,每个线程将获得1MB大小的栈空间。所以如果在系统中保留太多空闲线程对资源也是一种浪费。因此,CLR线程池在使用大量线程处理完大量任务之后,也会逐步地释放线程,直至到达最小值。CLR线程池的最小线程数量确保了在任务数量较少的情况下,新来的任务可以立即执行,从而省去了创建新线程的时间。在普通应用程序中这个值为“处理器数*1”,而在ASP.NET应用程序中这个值配置在machine.config文件中system.web/processModel节点的minWorkerThreads属性中。可惜的是,对于processModel节点的数据,ASP.NET只会读取machine.config中的全局配置信息,这意味着我们不能使用web.config为不同应用程序配置不同的参数。如果我们要实现应用程序级别的配置,那么必须使用ThreadPool类中提供的API进行设置:

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

值得注意的是,无论是设置还是获取到的这些数值,都与处理器数量没有任何关系了。

其中workerThreads参数便是CLR线程池的线程数,而completionPortThreads是IO完成线程池,这个异步编程里面重要的元素,简称:IOCP,异步优化全靠它了,以后在聊。

在某些时候可能会遇到这样的情况:在一个瞬间忽然来大量任务,每个任务的执行时间说长不长说短不短,不过足以导致线程池快速分配数百个线程。如果这个峰值之后就一片平静,那么势必造成大量空闲的线程,这种开销对性能的损耗也非常明显。因此,CLR线程池限制了线程的创建速度不超过每秒2个。这样,即使在某个瞬时获得了大量的任务,CLR线程池也可以使用相对较少的线程来完成所有工作。但是,还有一种情况也值得考虑。例如,对于一个比较繁忙的Web应用程序来说,一打开便会涌入大量的连接。由于线程的创建速度有限,因此可以执行的请求数量也只能慢慢增加。对于这种您预料到会产生大量线程,而且忙碌状况会持续一段时间的情况,限制线程的创建速度反而会带来损伤效率。这时,您就可以手动设置CLR线程池的最小线程数量。如果此时CLR线程池中拥有的线程数量较少,那么系统就会立即创建一定数量的线程来达到这个最小值。对于这点,您不妨来做一个算术题:线程池内一下子涌入了500个任务,每个任务阻塞或暂停5秒,每个线程占用1MB内存,假设线程池目前为空,且有着足够的容量,此外线程创建速度也足够快,那么在限制及不限制线程创建速度的情况下,完成这些任务需要多少时间和内存空间?

谢谢观看!

本文分享自微信公众号 - 明丰随笔(liumingfengwx2),作者:刘明丰

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-04-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • IOCP异步优化

    2. IO操作: CPU会把内存中的程序委托给其他的网络、磁盘等驱动程序,让这些外部的驱动程序来进行具体的处理,处理完成以后再返回给内存程序。对于这两类操作的优...

    小蜜蜂
  • CLR线程池边缘情况测试

    设置工作者线程的下限数量5,上限数量10,给线程池一瞬间安排20个任务,每个任务占用线程时间18秒,然后观察线程池在对待线程数量的上下限的反应,以及完成一个任务...

    小蜜蜂
  • 浅谈散列运算

    “指纹”一词形象地描述了散列运算的结果。在现实生活中,两个人可能长得很像,但是他们的指纹不同,根据指纹就能对这两个人进行区分。

    小蜜蜂
  • java多线程系列_线程简介(1)

        线程是程序运行的基本执行单元。当操作系统(不包括单线程的操作系统,如微软早期的DOS)在执行一个程序时,会在系统中建立一个进程,而在这个进程中,必须至少...

    Hongten
  • 多线程?怎么用?

    进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。

    故里
  • 读书笔记《Java并发编程的艺术 - 方腾飞》- Java并发编程

    在Java中, 我们可以通过 priority 属性来设置线程的优先级, 参数为 1 ~ 10 参数越大, 代表优先级越高, 默认的优先级为 5

    lvgo
  • 多线程开发,先学会线程池吧

    在实际开发场景中,我们经常要使用多线程开发应用,比如实现异步操作,或者为了提高程序的效率等等。但是以前我见过有实习生在使用的时候是直接new Runable()...

    java技术爱好者
  • 并发编程面试题汇总

    thinkwon.blog.csdn.net/article/details/104863992

    Java旅途
  • 小白科普:线程和线程池

    第一个问题问题就是为什么要多线程啊, 我看了操作系统中的多进程管理,不是挺好的吗? 多线程似乎没有必要啊!

    阿凯
  • Thread方法

    这个方法返回线程的 ID 值,类型为 long。线程的 ID 在线程的整个生命周期中都不变。

    宇宙之一粟

扫码关注云+社区

领取腾讯云代金券