优化线程池
线程池初始时其池内只有一个线程。随着任务的分配,线程池管理器就会向池内“注入”新线程来满足工作负荷的需要,直到最大数量的限制。在足够的非活动时间之后,线程池管理器在认为“回收”一些线程能够带来更好的吞吐量时进行线程回收。
可以通过调用ThreadPool.SetMaxThreads
方法来设置线程池可以创建的线程上限;默认如下:
- Framework 4.0,32位环境下:1023
- Framework 4.0,64位环境下:32768
- Framework 3.5:每个核心 250
- Framework 2.0:每个核心 25
(这些数字可能根据硬件和操作系统不同而有差异。)数量这么多是因为要确定阻塞(等待一些条件,比如远程计算机的相应)的线程的条件是否被满足。
也可以通过ThreadPool.SetMinThreads
设置线程数量下限。下限的作用比较奇妙:它是一种高级的优化技术,用来指示线程池管理器在达到下限之前不要延迟线程的分配。当存在阻塞线程时,提高下限可以改善程序并发性。
默认下限数量是 CPU 核心数,也就是能充分利用 CPU 的最小数值。在服务器环境下(比如 IIS 中的 ASP.NET),下限数量一般要高的多,差不多 50 或者更高。
最小线程数量是如何起作用的?
将线程池的最小线程数设置为 x 并不是立即创建至少 x 个线程,而是线程会根据需要来创建。这个数值是指示线程池管理器当需要的时候,立即 创建 x 个线程。那么问题是为什么线程池在其它情况下会延迟创建线程?
答案是为了防止短生命周期的任务导致线程数量短暂高峰,使程序的内存足迹(memory footprint)快速膨胀。为了描述这个问题,考虑在一个 4核的计算机上运行一个客户端程序,它一次发起了 40 个任务请求。如果每个任务都是一个 10ms 的计算,假设它们平均分配在 4 个核心上,总共的开销就是 100ms 多。理想情况下,我们希望这 40 个任务运行在 4 个线程上:
- 如果线程数量更少,就无法充分利用 4 个核心。
- 如果线程数量更多,会浪费内存和 CPU 时间去创建不必要的线程。
线程池就是以这种方式工作的。让线程数量和 CPU 核心数量匹配,就能够既保持小的内存足迹,又不损失性能。当然这也需要线程都能被充分使用(在这个例子中满足该条件)。
但是,现在来假设任务不是进行 10ms 的计算,而是请求互联网,使用半秒钟等待响应,此时本地 CPU 是空闲状态。线程池管理器的线程经济策略(译者注:指上面说的线程数量匹配核心数)这时就不灵了,应该创建更多的线程,让所有的请求同时进行。
幸运的是,线程池管理器还有一个后备方案。如果在半秒内没有能够响应请求队列,就会再创建一个新的线程,以此类推,直到线程数量上限。
半秒的等待时间是一把双刃剑。一方面它意味着一次性的短暂任务不会使程序快速消耗不必要的40MB(或者更多)的内存。另一方面,在线程池线程被阻塞时,比如在请求数据库或者调用WebClient.DownloadFile
,就进行不必要的等待。因为这种原因,你可以通过调用SetMinThreads
来让线程池管理器在分配最初的 x 个线程时不要等待,例如:
ThreadPool.SetMinThreads (50, 50);
(第二个参数是表示多少个线程分配给 I/O 完成端口(I/O completion ports,IOCP),来被APM使用
最小线程数量的默认值是 CPU 核心数。
文章地址:http://blog.gkarch.com/threading/part1.html#optimizing-the-thread-pool