今天一定要搞清楚如何设置线程池的线程数

1,425 阅读7分钟

大家都知道有个面试题叫做线程数量怎么设置

标准的面试八股文答案

假设机器有N个CPU,那么对于计算密集型的任务,应该设置线程数为N+1;对于IO密集型的任务,应该设置线程数为2N;对于同时有计算工作和IO工作的任务,应该考虑使用两个线程池,一个处理计算任务,一个处理IO任务,分别对两个线程池按照计算密集型和IO密集型来设置线程数。

来看看gpt的回答:

线程池是多线程编程中非常重要的一部分,它可以有效地管理线程的数量和执行。线程池的数量设置直接影响着程序的性能和稳定性。那么线程池的数量该怎么设置呢?

线程池的数量设置需要根据程序的实际情况来决定。一般来说,线程池的数量应该根据以下几个方面来考虑:

  1. CPU的核心数 线程池的数量应该和CPU的核心数相同或稍微多一点。这样可以充分利用CPU的资源,提高程序的运行效率。
  2. 程序的类型 不同类型的程序对线程池的需求也不同。比如IO密集型的程序,由于线程会被IO操作所阻塞,因此可以设置比较多的线程数;而CPU密集型的程序,则需要控制线程的数量,避免CPU资源过度占用。
  3. 内存资源 线程池的数量过多会占用过多的内存资源,因此需要根据程序的内存资源来考虑线程池的数量。
  4. 网络带宽 如果程序需要进行网络通信,线程池的数量也需要根据网络带宽来设定。如果网络带宽很大,可以适当增加线程池的数量,提高程序的并发处理能力。

综上所述,线程池的数量设置需要根据实际情况来决定,需要综合考虑CPU的核心数、程序的类型、内存资源和网络带宽等因素。只有合理设置线程池的数量,才能够充分发挥多线程的优势,提高程序的效率和稳定性。

2N和N+1是怎么来的?

计算密集型应用

对于计算密集型应用,假定等待时间趋近于0,CPU利用率达到100%,那么线程数就是CPU核心数,这个+1是为了确保在线程暂停时有一个额外的线程来保持CPU周期工作。所以N+1确实是一个经验值。

IO密集型应用

对于IO密集型应用,假定所有的操作时间几乎都是IO操作耗时,那么W/C的值就为1,那么对应的线程数确实为2N。

那么线程池的数量到底该设置为多少合适呢?

那假如在一个请求中,计算操作需要5ms,数据库操作需要100ms,对于一台8核CPU的服务器该如何设置线程数

首先这两个操作在我们日常的业务中可能多数情况下是不可拆分的,那么这时候怎么估算线程池的数量能让我们的资源最大利用?

按照八股文的标准答案我们想到的肯定是N+1或者2N那么这样看来就是9或者是16,那么我们取个最大值就来个16吧。这样真的对吗?

显然有问题,因为大家可以看一下开源框架里多线程的数量很少有设置为cpu*2的,那这又是为什么?

当请求过多,超出线程池负荷的时候,会触发拒绝策略。说先请求的时候就只能接受十几个请求,这个数量级是很难被业务接受的。现在很多并发系统不可能十几个请求就被拒绝,所以tomcat默认200线程数。同一时间可以接受200个请求

除了吞吐量的问题还有没有其他问题,有,2N和N+1是经验值,也是理论值。

计算密集型默认所有的时间都在处理计算

IO密集型默认所有的时间都在IO

这两个极端情况在我们的实际业务中其实不常见。我们常见的是既有IO又有计算,大多数情况计算量都是少的。

所以应该通过合理估算来设置线程池:

首先业务上大多情况是IO密集型的任务。如果按照IO密集型业务来设置那么就会出现一种情况

以上面的假设为例,首先把线程数设计为16个,这时候你可以用测试工具并发测试一下,你会发现你的CPU利用率上不去,同时请求的效率也不高。需要把线程数提高一些,比如40或者60的时候,CPU的利用率上去了,处理效率也可以了。

那么我们来看下这是为什么?

还是上面的假设,在处理一个请求的过程中,总的耗时是100+5=105ms,而其中只有5ms是用于计算操作的,CPU利用率为5/(100+5)=0.047。使用线程池是为了尽量提高CPU的利用率,减少对CPU资源的浪费,假设以100%的CPU利用率来说,想要达到100%的CPU利用率,对于一个CPU就要设置其利用率的倒数个数的线程数,也即1/(5/(100+5)),这就是在整个请求时间长度下每个cpu核心可以处理的线程数,那么如果是8核心的CPU那就乘以8。那么算下来的话,就是168,对,这个线程池要设置168个线程数。

内存资源

那么我们再看下gpt的回答其中提到了内存资源,如果是高并发系统,Java线程每创建1个就要消耗1M的内存空间,如果是高并发系统,1000个线程就要消耗1G内存资源,所以在这里来看设置线程数也不是越大越好,要考虑自己的内存资源够不够。

再就是会不会有其他限制呢?

比如你的数据库可能无法支撑你的高并发,如果数据库的QPS上限是1000这个时候怎么处理?

防止自己把数据库拖垮,我们应该按比例来减少线程数量,按照之前的计算过程,可以计算出来当线程数设置为168的时候,DB操作的QPS为,168*(1000/(100+5))=1600,如果现在DB的QPS最大为1000,那么对应的,最大只能设置168*(1000/1600)=105个线程。

所以来总结一下

如何设置计算线程池的线程数目的:

在一个基准负载下,使用 几种不同大小的线程池运行你的应用程序,并观察CPU利用率的水平。 给定下列定义: Ncpu = CPU的数量 Ucpu = 目标CPU的使用率, 0 <= Ucpu <= 1 W/C = 等待时间与计算时间的比率 为保持处理器达到期望的使用率,最优的池的大小等于:Nthreads = Ncpu x Ucpu x (1 + W/C)

如果觉得看不懂就看看上面的例子更好理解。

这种计算方式,我们需要知道上面定义的几个数值,才能计算出来线程池需要设置的线程数。其中,CPU数量是确定的,CPU使用率是目标值也是确定的,W/C也是可以通过基准程序测试得出的。

这样不管是面试还是实际业务中就再也不怕别人问你线程池数量怎么设置了吧!

加油! 雷哥出品,粗暴好用!呵呵哈哈哈!