CPU 密集型 和 IO密集型 的区别,如何确定线程池大小?

CPU 密集型

CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是CPU Loading 100%,CPU要读/写I/O(硬盘/内存),I/O在很短的时间就可以完成,而CPU还有许多运算要处理,CPU Loading很高。

比如说要计算1+2+3+…+ 1亿、计算圆周率后几十位、数据分析。
都是属于CPU密集型程序。

此类程序运行的过程中,CPU占用率一般都很高。

假如在单核CPU情况下,线程池有6个线程,但是由于是单核CPU,所以同一时间只能运行一个线程,考虑到线程之间还有上下文切换的时间消耗,还不如单个线程执行高效。

所以!!!单核CPU处理CPU密集型程序,就不要使用多线程了。

假如是6个核心的CPU,理论上运行速度可以提升6倍。每个线程都有 CPU 来运行,并不会发生等待 CPU 时间片的情况,也没有线程切换的开销。

所以!!!多核CPU处理CPU密集型程序才合适,而且中间可能没有线程的上下文切换(一个核心处理一个线程)。

简单的说,就是需要CPU疯狂的计算。

IO密集型

IO密集型指的是系统的CPU性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,但CPU的使用率不高。

所以用脚本语言像python去做I/O密集型操作,效率就很快。

简单的说,就是需要大量的输入输出,不如读文件、写文件、传输文件、网络请求。

区别和使用:

IO密集型:大量网络,文件操作
CPU 密集型:大量计算,cpu 占用越接近 100%, 耗费多个核或多台机器

业务要具体分析,假如CPU现在是10%,数据量增大一点点,CPU狂飙,那也可能CPU密集型。

如何确定线程池大小?

线程数不是越多越好。

由于CPU的核心数有限,线程之间切换也需要开销,频繁的切换上下文会使性能降低,适得其反。

简单的总结就是:

Ncpu 表示 核心数。

如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 Ncpu+1

如果是IO密集型任务,参考值可以设置为 2 * Ncpu

上面两个公式为什么是Ncpu+1 呢,而不是Ncpu+2 呢,为什么不是3 * Ncpu 呢?

在《Java并发编程实践》中,是这样来计算线程池的线程数目的:

一个基准负载下,使用 几种不同大小的线程池运行你的应用程序,并观察CPU利用率的水平。
给定下列定义:

Ncpu = CPU的数量
Ucpu = 目标CPU的使用率, 0 <= Ucpu <= 1
W/C = 等待时间与计算时间的比率

为保持处理器达到期望的使用率,最优的池的大小等于:

 Nthreads = Ncpu x Ucpu x (1 + W/C)

CPU数量是确定的,CPU使用率是目标值也是确定的,W/C也是可以通过基准程序测试得出的。

对于计算密集型应用,假定等待时间趋近于0,是的CPU利用率达到100%,那么线程数就是CPU核心数,那这个+1意义何在呢?

《Java并发编程实践》这么说:

计算密集型的线程恰好在某时因为发生一个页错误或者因其他原因而暂停,刚好有一个“额外”的线程,可以确保在这种情况下CPU周期不会中断工作。

所以 Ncpu+1 是一个经验值。

对于IO密集型应用,假定所有的操作时间几乎都是IO操作耗时,那么 W/C的值就为1,Ucpu 要达到100%利用率。

根据 Nthreads = Ncpu x Ucpu x (1 + W/C),

那么对应的线程数确实为 2Ncpu

Java代码中可以通过Rumtime来获得CUP的数目:

int N_CPUS = Runtime.getRuntime().availableProcessor();

对于包含I/O操作或者其他阻塞的任务,由于线程不会一直执行,因此线程池的数量应该更多。

在《linux多线程服务器端编程》中有一个思路,CPU计算和IO的阻抗匹配原则

如果线程池中的线程在执行任务时,密集计算所占的时间比重为P(0

这个经验公式的原理很简单,T个线程,每个线程占用P的CPU时间,如果刚好占满C个CPU,那么必有 T * P = C。

如果一个web程序有CPU操作,也有IO操作,那该如何设置呢?

有一个估算公式:

最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目

这个公式进一步转化为:

最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* 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时间所占比例越高,需要越少线程。

是否使用线程池就一定比使用单线程高效呢?

答案是否定的,比如Redis就是单线程的,但它却非常高效,基本操作都能达到十万量级/s。从线程这个角度来看,部分原因在于:

  • 多线程带来线程上下文切换开销,单线程就没有这种开销

总结

即使有上面的简单估算方法,也许看似合理,但实际上也未必合理,都需要结合系统真实情况(比如是IO密集型或者是CPU密集型或者是纯内存操作)和硬件环境(CPU、内存、硬盘读写速度、网络状况等)来不断尝试达到一个符合实际的合理估算值。

你可能感兴趣的:(Java,java,多线程,IO,CPU密集型,IO密集型)