如何确定线程池大小?
理解两个概念
CPU密集型和IO密集型
-
CPU密集型是指你提交的任务I/O在很短的时间就可以完成,而主要的时间都用在CPU的运算上了,例如计算1+2+3+4…+n,假设这个n是1亿;在这类任务中都需要非常多的CPU资源去进行运算(CPU占用率高),就叫做CPU密集型任务
-
IO密集型是指你提交的任务CPU占用率很低,而主要的时间都用在了等待I/O上了,比如网络I/O,文件I/O,就叫做IO密集型任务
这两者的区别对设置线程池大小有什么影响?
先说CPU密集型任务,对于CPU密集型任务,它对CPU的占用率是非常高的,假设一个任务在单线程的环境下需要执行10秒,那如果在单核CPU下,同时只能有一个线程执行任务,我把线程池大小设置为n,并且每个线程池中的线程都有任务,就意味着这n个线程需要频繁的切换上下文去竞争CPU的资源;最终的执行耗时绝对大于10n秒,因为部分时间消耗在线程上下文切换了;so,总结一下,⚠️如果你的服务器是单核的CPU,并且任务还是CPU密集型的,建议不要上多线程,如果CPU是多核的可以考虑上多线程,尽量让每一个线程占用一个CPU,避免线程的上下文切换,提高计算效率
public void work() {
// 阻塞等待IO,耗时10s
}
再说IO密集型任务,因为IO密集型任务其对CPU的占用率较少,主要的时间在等待IO上,那么此时可以考虑多使用几个线程,对CPU的竞争较小
那具体怎么设置线程池的大小呢?
👆说了,线程池并不是设置的越大越好,因为存在线程上下文切换,并且创建线程本身就对系统的资源有较大的消耗(给线程开辟独立内存空间等)
直接说结论
Ncpu 表示CPU核心数
如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 Ncpu+1
如果是IO密集型任务,参考值可以设置为 ((线程等待时间+线程CPU时间)/线程CPU时间 ) Ncpu*
为什么CPU密集型任务线程数还要在Ncpu上+1?
不是说尽量保持每个线程占用一个CPU,那为什么不设置成Ncpu?
在《Java并发编程实践》中,是这样来计算线程池的线程数目的:
一个基准负载下,使用 几种不同大小的线程池运行你的应用程序,并观察CPU利用率的水平。 给定下列定义:
Ncpu = CPU的数量
Ucpu = 目标CPU的使用率, 0 <= Ucpu <= 1
W/C = 等待时间与计算时间的比率
为保持处理器达到期望的使用率,最优的池的大小等于:
Nthreads = Ncpu x Ucpu x (1 + W/C)
对于CPU密集型的任务,假定等待时间趋近于0,CPU利用率达到100%,那么核心线程数就是CPU核心线程数,那也不是Ncpu+1啊
《Java并发编程实践》这么说:
计算密集型的线程恰好在某时因为发生一个页错误或者因其他原因而暂停,刚好有一个“额外”的线程,可以确保在这种情况下CPU周期不会中断工作。
所以 Ncpu+1 是一个经验值。
为什么IO密集型任务线程数 为((线程等待时间+线程CPU时间)/线程CPU时间 )* Ncpu
在《linux多线程服务器端编程》中有一个思路,CPU计算和IO的阻抗匹配原则。
如果线程池中的线程在执行任务时,密集计算所占的时间比重为P(0<P<=1),而系统一共有C个CPU,为了让CPU跑满而又不过载,线程池的大小经验公式 T = C / P。在此,T只是一个参考,考虑到P的估计并不是很准确,T的最佳估值可以上下浮动50%。
这个经验公式的原理很简单,T个线程,每个线程占用P的CPU时间,如果刚好占满C个CPU,那么必有 T * P = C。
如果一个web程序有CPU操作,也有IO操作,那该如何设置呢?
有一个估算公式:
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
举例
问题一:
假如一个程序平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么最佳的线程数应该是?
根据上面这个公式估算得到最佳的线程数:((0.5+1.5)/0.5)*8=32
问题二:
假如在一个请求中,计算操作需要5ms,DB操作需要100ms,对于一台8个CPU的服务器,总共耗时100+5=105ms,而其中只有5ms是用于计算操作的,CPU利用率为5/(100+5)。使用线程池是为了尽量提高CPU的利用率,减少对CPU资源的浪费,假设以100%的CPU利用率来说,要达到100%的CPU利用率,又应该设置多少个线程呢?
((5+100)/5)*8=168 个线程。
问题三:
那如果现在这个IO操作是DB操作,而DB的QPS上限是1000,这个线程池又该设置为多大呢?
这个可以安装比例进行,根据上面算出168最大的线程数,可以反推出DB的最大QPS: 168*(1000/(100+5))=1600 如果现在DB的QPS最大为1000,那么对应的,最大只能设置168*(1000/1600)=105个线程
以上可以得出一个结论:
线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。
总结
- CPU密集型,在CPU多核情况下,建议将线程池大小设置为2Ncpu + 1
- I O密集型,建议将线程池大小设置为((线程等待时间+线程CPU时间)/线程CPU时间 )* Ncpu
参考链接:https://cloud.tencent.com/developer/article/1806245