线程池大小设置多少,比较合适?

news2025/1/12 18:40:09

设置线程数的核心点

压测!压测!再压测!实际对性能要求比较高的场景,压测是最佳的方式!

并发编程适用于什么场景?

CPU 密集型

对于 CPU 密集型任务,希望最大限度地提高 CPU 利用率,但又不会因为过多的线程而压垮系统,否则会导致过多的上下文切换。

使用场景:加密、解密、压缩、计算等一系列需要大量耗费 CPU 资源的任务。

确定线程池大小的公式如下:

线程数 = Ncpu

线程数 = Ncpu + 1

Ncpu:CPU 核数

// 获取CPU内核数量
int number = Runtime.getRuntime().availableProcessors();

对于密集型的任务,应用程序的最小线程数应该等于可用的处理器核数,如果所有任务都是密集型的,处理器的系统通常通过使用 Ncpu + 1 个线程的线程池来获得最优的利用率(计算密集型的线程恰好在某时因为发生一个页错误或者因其他原因而暂停,刚好有一个“额外”的线程,可以确保在这种情况下CPU周期不会中断工作)。

在这种情况下,创建更多的线程对程序性能而言反而是不利的。因为当有多个任务处于就绪状态时,处理器核心需要在线程间频繁进行上下文切换,而这种切换对程序性能损耗较大。但如果任务被阻塞的时间大于执行时间,即该任务是 I/O 密集型的,我们就需要创建更多的线程来提高性能。

I/O密集型

对于 I/O 密集型任务,最佳线程数通常由 I/O 操作的性质和预期延迟决定。希望有足够的线程来保持 I/O 设备繁忙而不会使它们过载。

使用场景:数据库、文件的读写,网络通信等任务。这种任务的特点是并不会特别消耗 CPU 资源,但是 IO 操作很耗时,总体会占用比较多的时间。

当一个任务执行IO操作时,其线程被阻塞,处理器立即进行上下文切换以便处理其他就绪线程。如果只有处理器可用核心数个线程的话,则即使有待执行的任务也无法处理,因为已经拿不出更多的线程供处理器调度了。

对于包含了 I/O 和其他阻塞操作的任务,因此你需要一个更大的池子。为了正确地设置线程池的长度,你必须估算出任务花在等待的时间与用来计算的时间的比率;这个估算值不必十分精确,而且可以通过一些监控工具获得。你还可以选择另一种方法来调节线程池的大小,在一个基准负载下,使用 几种不同大小的线程池运行你的应用程序,并观察CPU利用率的水平。

公式一

线程数 = Ncpu * Ucpu * (1 + 平均等待时间 / 平均工作时间)

Ucpu:又称 CPU 利用率, 这是应用程序使用 CPU 时间的百分比。取值范围:0 <= Ucpu <= 1

线程的等待时间:线程的等待时间指的是线程在等待某个条件满足或等待其他线程完成时所花费的时间。在等待时间内,线程可能被挂起,不占用 CPU 资源。通常发生在等待某个条件、等待 I/O 操作完成、等待锁释放等情况下。在这段时间内,线程可能不执行任务。

示例:

synchronized (lock) {
    while (!condition) {
        try {
            lock.wait(); // 等待条件满足
            // 在这里,线程处于等待状态
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
// 在这里,等待完成后线程继续执行工作

线程的工作时间:线程的工作时间指的是线程正在执行任务的时间,也称为 CPU 时间。在这段时间内,线程在处理器上执行指令,执行其分配的任务。

示例:

Thread workerThread = new Thread(() -> {
    // 线程的工作时间
    // 执行任务
});

workerThread.start();
// 在这里,workerThread 正在工作

通过这个公式,可以计算出一个合理的线程数量,如果任务的平均等待时间长,线程数就随之增加;如果平均工作时间长,也就是上面的 CPU 密集型任务,线程数就随之减少。

假设 Ncpu=12,多个任务运算占总时间的 50%,可以运行的 线程数= 12 x 1 x (1 + 0.5 / 0.5) = 24 个线程。

公式二

线程数 = Ncpu / (1 - 阻塞系数) =  Ncpu * Ucpu = Ncpu * (Ucpu / (1 - 阻塞系数))

阻塞系数: 这是等待时间与服务时间的比率,它衡量线程等待 I/O 操作完成所花费的时间相对于执行计算所花费的时间,阻塞系数的取值在0和1之间(密集型任务的阻塞系数为0,而 I/O 密集型任务的阻塞系数则接近1)。

计算这些类型资源池的大小约束非常简单:首先累加出每一个任务需要的这些资源的总量,然后除以可用的总量,所得的结果是池大小的上限。线程数太少会使得程序整体性能降低,而过多的线程也会消耗内存等其他资源,所以如果想要更准确的话,必须进行压测,并监控 JVM 的线程情况以及 CPU 的负载情况,根据实际情况衡量应该创建的线程数,合理并充分利用资源。

当任务需要使用池化的资源时,比如数据库连接,那么线程池的长度和资源池的长度会相互影响。如果每一个任务都需要一个数据库连接,那么连接池的大小就限制了线程池的有效大小;类似的,当线程池中的任务是连接池的唯一消费者时,那么线程池的大小反而又会限制了连接池的有效大小。

假设 Ncpu=12,多个任务阻塞率是 50%,可以运行的 线程数= 12 / (1 - 0.5) = 24 个线程。

综上所述得出如下结论:

如上所述的估算线程池大小公式:

线程数 = Ncpu /(1 - 阻塞系数)

对于公式一,假设 CPU 100% 运转,撇开 CPU 使用率因素,即:

线程数 = CPU核数 x (1 + 平均等待时间 / 平均工作时间)

现在假设将公式二的公式等于公式一,即

CPU核数 /(1 - 阻塞系数)= CPU核数 x (1 + 平均等待时间 / 平均工作时间)

推导出:

阻塞系数 = 平均等待时间 / (平均等待时间 + 平均工作时间)

阻塞系数 = 阻塞时间 /(阻塞时间 + 计算时间)

如下:

由于对 Web 服务的请求大部分时间都花在等待服务器响应上了,所以阻塞系数会相当高,因此程序需要开的线程数可能是处理器核心数的若干倍。

假设阻塞系数是 0.9,即每个任务 90% 的时间处于阻塞状态而只有 10% 的时间在干活,则在双核处理器上我们就需要开 20 个线程。假若有很多任务要处理的话,一个 8 核处理器上开到 80 个线程来处理该任务。所以:

  • 线程的平均工作时间所占比例越高,就需要越少的线程;
  • 线程的平均等待时间所占比例越高,就需要越多的线程;
  • 针对不同的程序,进行对应的实际测试就可以得到最合适的选择。

实际应用

下面应用中的计算都以 Ncpu = 12 做计算。

实例:假设一台服务器期望应用程序使用 50% 的可用 CPU 资源,怎么设计?

应用程序有两类任务:I/O 密集型任务和 CPU 密集型任务。

I/O 密集型任务的阻塞系数为 0.5,这意味着要花费 50% 的时间等待 I/O 操作完成。

线程数 = Ncpu * 0.5 * (1 + 0.5) = 9

CPU 密集型任务的阻塞系数为 0.1,这意味着要花费 10% 的时间等待 I/O 操作完成。

线程数 = Ncpu * 0.5 * (1 + 0.1) = 7

在此示例中,创建两个线程池,一个用于 I/O 密集型任务,另一个用于 CPU 密集型任务。I/O 密集型线程池将有 9 个线程,CPU 密集型线程池将有 7 个线程。

实例:假设一台服务器平均每秒处理一个事务的时间为 400ms,期望应用程序达到 20TPS(TPS:Transaction Per Second),怎么设计?

为了使应用程序能够达到 20TPS,首先计算出每个事务的平均处理时间,公式如下:

每个事务的平均处理时间(秒)= 每个事务的平均处理时间(毫秒)/ 1000ms

再计算每秒处理的事务量,公式如下:

每秒处理的事务量 = Ncpu * (1 / 每个事务的平均处理时间(秒))

因此,每秒处理事务的数量为:Ncpu * (1 / 0.4) = 12 * 2.5 = 30 个,可见已经高于期望目标了。

实例:假设一台服务器的平均工作时间(非阻塞时间或又称 CPU 调试时间)需要 5ms,DB 平均操作时间(I/O阻塞时间)需要 200ms,怎么设置线程大小?

一个线程的平均执行时间为:5 + 200 = 205ms。

按 CPU 使用率 100% 计算,公式如下:

线程数 = Ncpu * (1 + 平均等待时间 / 平均工作时间)

因此,线程数为: 12 * (1 + 200 / 5) = 492 个。

实例:结合上个实例,如果 DB 的上限是 1000QPS(Query Per Second),此时又该如何设置这个线程大小?

首先将每个查询的平均处理时间转换为秒,公式如下:

每个查询的平均处理时间(秒)= 每个查询的平均处理时间(毫秒)/ 1000ms

再计算每秒处理的查询量,公式如下:

每秒处理的查询量 = Ncpu * (1 / 每个查询的平均处理时间(秒))

因此,每秒处理的查询量为:Ncpu * (1 / 0.205) = 12 * 4.87 = 58.536 个。

那么如果使用上个实例算出的线程数(492),则每秒可处理的查询数量的公式如下:

每秒可处理的查询数量 = 线程数 * (1 / 每个查询的平均处理时间(秒))

因此,每秒可处理查询的数量是:492 * 1000 / 205 = 2400QPS。

此时每秒可处理查询的数量已经大于了 DB 的上限,所以线程数就要等比例减少,公式为如下:

新线程数 = 线程数 * (DB 的上限 / 每秒可处理的查询数量)

因此,新线程数为:492 * 1000 / 2400 = 205 个。
 

实例:假设一台服务器平均每秒处理一个查询的时间为 200ms,且这些查询没有 CPU 阻塞,期望应用程序达到 1000QPS(QPS:Query Per Second),怎么设计?

注意:需求已经查询表明 CPU 无阻塞的问题,可以理解为 CPU 密集型,所以就就意味着:线程数=Ncpu(如果为防止线程意外停止即:线程数 = Ncpu + 1)。

首先先将每个查询的平均处理时间转换为秒,公式如下:

每个查询的平均处理时间(秒)= 每个查询的平均处理时间(毫秒)/ 1000ms

再计算每秒处理的查询量,公式如下:

每秒处理的查询量 = Ncpu * (1 / 每个查询的平均处理时间(秒))

因此,每秒处理查询的数量为:Ncpu * (1 / 0.2) = 12 * 5 = 60 个。

由此可以看见 1000QPS 指标与单台服务器 60 个查询线程之间的差距,要想达到期望指标仅靠一台服务器是远远达不到的,这时就需要扩容服务器来达到所期望目标。

为了计算需要扩容多少台服务器,公式如下:

总服务器数量 = 每秒总查询量 / 每秒处理的查询量

因此,要想达到为 1000QPS 的指标需要 1000 / 60 = 17 台服务器才能达成目标。

实例:假设一台服务器平均每秒处理一个查询的时间为 200ms,且查询时间占总时间的 90%,期望应用程序达到 1000QPS(QPS:Query Per Second),怎么设计?

首先计算每个查询的实际处理时间,考虑到这些查询占总时间的90%,公式如下:

每个查询的实际处理时间 = 每个查询的平均处理时间 / 占总时间的比例 

因此,每个查询的实际处理时间为:200ms / 0.9 = 222.22ms

再计算每秒处理的查询量,公式如下:

每秒处理的查询量 = Ncpu * (1 / 每个查询的实际处理时间(秒))

因些,每秒处理的查询量为:Ncpu * (1 / 0.222) = 12 * 4.5 = 54 个查询。由此可以看见 1000QPS 指标与单台服务器 54 个查询线程之间的差距,要想达到期望指标仅靠一台服务器是远远达不到的,这时就需要扩容服务器来达到所期望目标。

为了计算需要扩容多少台服务器,公式如下:

总服务器数量 = 每秒总查询量 / 每秒处理的查询量

因此,要想达到为 1000QPS 的指标需要 1000 / 54 = 19 台服务器才能达成目标。

阿姆达尔定律(Amdahl)

根据上面的信息得出一个想法:如果增加 N 个 CPU 核数后性能是不是会嗖嗖嗖往上嗖?先不做问题解答,因为想要弄明白这个需要先弄明白 阿姆达尔定律(Amdahl) 是什么才能更好的理解。

什么是阿姆达尔定律(Amdahl)?

旨在用公式描述在并行计算中,多核处理器理论上能够提高多少倍速度。它代表了处理器并行运算之后效率提升的能力。

公式如下:

S(N)=\frac{1}{1-P+\frac{P}{N}}

说明:

  • S(N):是加速比(Speedup)
  • P:是并行化部分占总程序执行时间的比例
  • N:是并行化部分的处理器数目

加速比(Speedup enhanced

系统原来串行计算需要 6s,加速后只需要 3s,即:S。

计算公式如下:

加速比 = 原有运行时间(顺序执行的时间) / 并行计算加速后的时间 

因为,加速比为:6 / 3 = 2,由此可知加速比的值永远大于1。

部分提高(Fraction enhanced

部分提高是并行化部分占总程序执行时间的比例,即:P。

假若程序总共有 100 行代码,其中 50 行是可以通过并行计算的,那么这 50 行代码就是部分提高。但是实际上部分提高是一个比例数值,是并行计算代码 / 总代码量。

公式如下:

部分提高 = 串行代码 / 总代码量

因此,部分提高为:50 / 100 = 0.5,由此可见部分提高的值永远小于 1。

带入 阿姆达尔定律

分别把 部分提高 和 加速比增强 带入阿姆达尔定律中。部分提高 对应公式中的,即并行计算所占比例。加速比增强 对应,即并行节点处理个数。

加速比增强 为什么可以代替 Ncpu ?

想必这里可能有一点疑问,为什么 加速比增强 = 未加速前时间 / 加速后的时间,为什么就可以代表并行节点处理个数?

在理论上,单核处理器处理一个任务需要 200ms,那么双核处理它应该需要 100ms。时间上提速了2倍, CPU 个数上也提升了 2 倍,故两个可以替换。

总结

P 为并行计算所占比例,N 为并行节点处理个数。

当 1 - P = 0 时,没有串行,只有并行,最大加速比 S(N) = N;当 P = 0 时,只有串行,没有并行,最小加速比 S(N) = 1;当 cpu 核心数无限增多的时候,极限加速比 S(N) = 1 / (1 - P),这也就是加速比的上限。

由此我们可知,在并行系统中一味的增加运算资源,并不能永远成倍的提升系统整体性能。

实际应用

实例:假设查询操作占整体系统运行流程比例的 50%(P=0.5),并且为了保持 1000QPS,想要计算提升比例?

可以考虑增加并行处理的处理器数目,即 N。

公式如下:

S(N)=\frac{1}{1-0.5+\frac{0.5}{N}}

现在,我们可以通过计算不同 N 值下的加速比,找到一个合适的 N 值以实现整体性能提升。例如,当 N=12 时,公式如下:

S(12)=\frac{1}{1-0.5+\frac{0.5}{12}}=\frac{1}{0.5+0.0417}=1.8460

以上结果说明即使保证了 1000QPS,整体提升比例仍不超过2倍。

实例:假设查询操作占整体系统运行流程比例的 90%(P=0.9),并且为了保持 1000QPS,想要计算提升比例?

可以考虑增加并行处理的处理器数目,即 N。

公式如下:

S(N)=\frac{1}{1-0.9+\frac{0.9}{N}}

现在,我们可以通过计算不同 N 值下的加速比,找到一个合适的 N 值以实现整体性能提升。例如,当 N=12 时,公式如下:

S(12)=\frac{1}{1-0.9+\frac{0.9}{12}}=\frac{1}{0.1+0.0083}=9.2334

以上结果说明即使保证了 1000QPS,整体提升比例可以达到近10 倍。

通过计算不同 N 值下的加速比,你可以看到整体系统的性能提升。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1281790.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用String.valueOf()的坑

说明&#xff1a;记录一次使用String.valueOf()的坑&#xff0c;以下是一段有问题的代码&#xff1a; String count String.valueOf(listData.get(0).get(0).get("count");if (StringUtils.isBlank(count) || "0".equals(count)) {result.setResult(page)…

LLM推理部署(五):AirLLM使用4G显存即可在70B大模型上进行推理

众所周知&#xff0c;大模型的训练和推理需要大量的GPU资源&#xff0c;70B参数的大模型需要130G的GPU显存来存储&#xff0c;需要两个A100&#xff08;显存为100G&#xff09;。 ​ 在推理过程中&#xff0c;整个输入序列也需要加载到内存中进行复杂的“注意力”计算&am…

【WinForm.NET开发】演示:创建一个图片查看器 Windows 窗体应用

本文演示将创建一个 Windows 窗体应用程序&#xff0c;用于加载和显示图片。 Visual Studio 集成设计环境 (IDE) 提供了创建应用所需的工具。 1、先决条件 若要完成本教程&#xff0c;必须具有 Visual Studio。 请访问Visual Studio 下载页获取免费版本。 2、创建 Windows …

C语言扫雷游戏

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、扫雷游戏的分析和设计1.1扫雷游戏的功能说明1.2数据结构的分析1.3文件结构设计 二、扫雷游戏的代码实现总结 前言 详细介绍扫雷游戏的思路和实现过程。 一…

基于Java SSM框架实现美好生活九宫格日志网站系统项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架实现美好生活九宫格日志网站系统演示 摘要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人…

风险评估有什么用

风险评估就是量化测评某一事件或事物带来的影响或损失的可能程度。 为什么要做风险评估? 1.更准确地认识风险-系统地评估资产风险事件发生的概率大小和概率分布&#xff0c;及发生后损失的严重程度。帮助区分主要风险和次要风险。 2.保证规划的合理性和可行性-正确反映各风…

【开源】基于Vue.js的智慧社区业务综合平台

文末获取源码&#xff0c;项目编号&#xff1a; S 077 。 \color{red}{文末获取源码&#xff0c;项目编号&#xff1a;S077。} 文末获取源码&#xff0c;项目编号&#xff1a;S077。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 业务类型模块2.2 基础业务模块2.3 预…

网络安全--网络环境构成,系统的安全

2. 网络攻防环境 目标 了解攻防环境构成了解入侵检测系统&#xff08;平台&#xff09;的部署位置 2.1. 环境构成 2.1.1. 环境框图 一个基本的网络攻防实验环境包括&#xff1a;靶机、攻击机、入侵检测分析系统、网络连接四部分组成。 一个基础的网络攻防实验环境需要如下…

Java 学习之多态

多态的概念 多态 晚绑定。 所谓多态&#xff0c;就是父类型的引用可以指向子类型的对象&#xff0c;或者接口类型的引用可以指向实现该接口的类的实例。 不要把函数重载理解为多态。因为多态是一种运行期的行为&#xff0c;不是编译期的行为。 多态&#xff1a;父类型的引用可…

保育员个人简历精选7篇

想要在保育员职位的求职过程中脱颖而出吗&#xff0c;参考这7篇精选的保育员简历案例&#xff01;无论您的经验如何&#xff0c;都能找到适合自己的简历样式及参考内容。 保育员个人简历模板下载&#xff08;可在线编辑制作&#xff09;&#xff1a;来幻主简历&#xff0c;做好…

Shell循环:expect(二)

expect实战&#xff1a;公钥推送 一、准备工作&#xff1a;安装expect&#xff0c;装备公钥 二、通过shell循环判断主机在线 #!/bin/bash #脚本编写 #创建一个IP地址文件 >ip.txt #使用for循环ping测试主机是否在线 for i in {3..254} do{ip192.168.151.$iping -c1 -W…

矢量图形设计软件CorelDRAW 2023 mac界面说明

CorelDRAW 2023 mac是一款专业的矢量图形设计软件&#xff0c;由Corel公司开发。它提供了广泛的创意工具和功能&#xff0c;旨在满足设计师、艺术家和创意专业人士的需求。 CorelDRAW 2023具有直观的用户界面和工作流程&#xff0c;使用户能够轻松创建各种类型的图形设计&#…

三十六、seata的部署和集成

seata的部署和集成 一、部署Seata的tc-server 1.下载 首先我们要下载seata-server包&#xff0c;地址在http&#x1f615;/seata.io/zh-cn/blog/download.html 当然&#xff0c;资料也准备好了&#xff1a; 2.解压 在非中文目录解压缩这个zip包&#xff0c;其目录结构如下…

如何开启Windows Server 2016 远端桌面

使用GUI 设定 服务器管理器–> 本地服务器–> 远端桌面 启用远端桌面 远端–> 允许远端连线至此电脑 会提示防火墙设定跟电源设定 防火墙之前已经关闭了 完成

线程中出现异常的处理

目录 前言 正文 1.线程出现异常的默认行为 2.使用 setUncaughtExceptionHandler() 方法进行异常处理 3.使用 setDefaultUncaughtExceptionHandler() 方法进行异常处理 4.线程组内处理异常 5.线程异常处理的优先性 总结 前言 在紧密交织的多线程环境中&#xff0c;异…

游戏配置表的导入使用

游戏配置表是游戏策划的标配&#xff0c;如下图&#xff1a; 那么程序怎么把把这张配置表导入使用&#xff1f; 1.首先&#xff0c;利用命令行把Excel格式的文件转化成Json格式&#xff1a; json-excel\json-excel json Tables\ Data\copy Data\CharacterDefine.txt ..\Cli…

如何从 Jira 成功迁移到极狐GitLab,看这个就够了!

内容来源&#xff1a;https://about.gitlab.com/blog 作者&#xff1a;Melissa Ushakov Atlassian 之前表示&#xff0c;到 2024 年 2 月会全面终止对于其服务器端产品的支持。 随着 Jira Server 的生命周期即将结束&#xff0c;众多组织都在考虑将其敏捷项目管理工具从Jira 迁…

Linux shell编程学习笔记32:declare 命令

0 前言 在 Linux shell编程学习笔记16&#xff1a;bash中的关联数组https://blog.csdn.net/Purpleendurer/article/details/134053506?spm1001.2014.3001.5501 中&#xff0c;我们在定义关联数组时使用了declare命令。 其实&#xff0c;declare命令的功能不只是定义定义关…

【蓝桥杯选拔赛真题73】Scratch烟花特效 少儿编程scratch图形化编程 蓝桥杯创意编程选拔赛真题解析

目录 scratch烟花特效 一、题目要求 编程实现 二、案例分析 1、角色分析

C#,数值计算——插值和外推,二维三次样条插值(Spline2D_interp)的计算方法与源程序

1 文本格式 using System; namespace Legalsoft.Truffer { /// <summary> /// 二维三次样条插值 /// Object for two-dimensional cubic spline interpolation on a matrix.Construct /// with a vector of x1 values, a vector of x2 values, and a ma…