并发下线程池的最佳数量计算
在高并发的情况下采用线程池,有效的降低了线程创建释放的时间花销及资源开销,如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及”过度切换”。(在JVM中采用的处理机制为时间片轮转,减少了线程间的相互切换)
那么在高并发的情况下,我们怎么选择最优的线程数量呢?选择原则又是什么呢?这个问题去哪网的技术总监问过我,这里总结一下。
第一种:
如果是CPU密集型应用,则线程池大小设置为N+1;(对于计算密集型的任务,在拥有N个处理器的系统上,当线程池的大小为N+1时,通常能实现最优的效率。(即使当计算密集型的线程偶尔由于缺失故障或者其他原因而暂停时,这个额外的线程也能确保CPU的时钟周期不会被浪费。摘自《Java Concurrency In Practise》)
如果是IO密集型应用,则线程池大小设置为2N+1
- 1
- 2
任务一般可分为:CPU密集型、IO密集型、混合型,对于不同类型的任务需要分配不同大小的线程池。CPU密集型任务 尽量使用较小的线程池,一般为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,只能增加上下文切换的次数,因此会带来额外的开销。IO密集型任务 可以使用稍大的线程池,一般为2*CPU核心数。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候去处理别的任务,充分利用CPU时间。混合型任务 可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。 只要分完之后两个任务的执行时间相差不大,那么就会比串行执行来的高效。 因为如果划分之后两个任务执行时间相差甚远,那么先执行完的任务就要等后执行完的任务,最终的时间仍然取决于后执行完的任务,而且还要加上任务拆分与合并的开销,得不偿失。
第二种呢,先由之前遇到的一个测试题说起,假设要求一个系统的TPS(Transaction Per Second或者Task Per Second)至少为20,然后假设每个Transaction由一个线程完成,继续假设平均每个线程处理一个Transaction的时间为4s。那么问题转化为:
如何设计线程池大小,使得可以在1s内处理完20个Transaction?
计算过程很简单,每个线程的处理能力为0.25TPS,那么要达到20TPS,显然需要20/0.25=80个线程。
这个理论上成立的,但是实际情况中,一个系统最快的部分是CPU,所以决定一个系统吞吐量上限的是CPU。增强CPU处理能力,可以提高系统吞吐量上限。在考虑时需要把CPU吞吐量加进去。在IO优化文档中,有这样地公式:
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
即线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。
但根据短板效应,真实的系统吞吐量并不能单纯根据CPU来计算。那要提高系统吞吐量,就需要从“系统短板”(比如网络延迟、IO)着手:
尽量提高短板操作的并行化比率,比如多线程下载技术
增强短板能力,比如用NIO替代IO
尽量提高短板操作的并行化比率,比如多线程下载技术
增强短板能力,比如用NIO替代IO
- 1
- 2
- 3
- 4
- 5
- 6
- 7
第一条可以联系到Amdahl定律,这条定律定义了串行系统并行化后的加速比计算公式:
加速比=优化前系统耗时 / 优化后系统耗时
- 1
- 2
加速比越大,表明系统并行化的优化效果越好。Addahl定律还给出了系统并行度、CPU数目和加速比的关系,加速比为Speedup,系统串行化比率(指串行执行代码所占比率)为F,CPU数目为N:
Speedup <= 1 / (F + (1-F)/N)
- 1
- 2
当N足够大时,串行化比率F越小,加速比Speedup越大。
这时候又抛出是否线程池一定比但线程高效的问题?
答案是否定的,比如Redis就是单线程的,但它却非常高效,基本操作都能达到十万量级/s。从线程这个角度来看,部分原因在于:
多线程带来线程上下文切换开销,单线程就没有这种开销
锁
- 1
- 2
- 3
当然“Redis很快”更本质的原因在于:
Redis基本都是内存操作,这种情况下单线程可以很高效地利用CPU。而多线程适用场景一般是:存在相当比例的IO和网络操作。
总的来说,应用情况不同,采取多线程/单线程策略不同;线程池情况下,不同的估算,目的和出发点是一致的。
参考借鉴:http://ifeve.com/how-to-calculate-threadpool-size/
https://www.zhihu.com/question/38128980