别再纠结线程池大小 + 线程数量了,没有固定公式的

news2025/1/14 1:10:27

可能很多人都看到过一个线程数设置的理论:

  • CPU 密集型的程序 - 核心数 + 1
  • I/O 密集型的程序 - 核心数 * 2

不会吧,不会吧,真的有人按照这个理论规划线程数?

线程数和CPU利用率的小测试

抛开一些操作系统,计算机原理不谈,说一个基本的理论(不用纠结是否严谨,只为好理解):
一个CPU核心,单位时间内只能执行一个线程的指令

那么理论上,我一个线程只需要不停的执行指令,就可以跑满一个核心的利用率。

来写个死循环空跑的例子验证一下:

测试环境:AMD Ryzen 5 3600, 6 - Core, 12 - Threads

public class CPUUtilizationTest {
    public static void main(String[] args) {
        //死循环,什么都不做
        while (true){
        }
    }
}

运行这个例子后,来看看现在CPU的利用率:

 


从图上可以看到,我的3号核心利用率已经被跑满了

那基于上面的理论,我多开几个线程试试呢?

public class CPUUtilizationTest {
 public static void main(String[] args) {

  for (int j = 0; j < 6; j++) {
   new Thread(new Runnable() {
    @Override
    public void run() {
     while (true){
     }
    }
   }).start();
  }
 }
}

此时再看CPU利用率,1/2/5/7/9/11 几个核心的利用率已经被跑满:

 


那如果开12个线程呢,是不是会把所有核心的利用率都跑满?答案一定是会的:

 


如果此时我把上面例子的线程数继续增加到24个线程,会出现什么结果呢?

 


从上图可以看到,CPU利用率和上一步一样,还是所有核心100%,不过此时负载已经从11.x增加到了22.x(load average解释参考scoutapm.com/blog/unders…),说明此时CPU更繁忙,线程的任务无法及时执行。

现代CPU基本都是多核心的,比如我这里测试用的AMD 3600,6核心12线程(超线程),我们可以简单的认为它就是12核心CPU。那么我这个CPU就可以同时做12件事,互不打扰。

如果要执行的线程大于核心数,那么就需要通过操作系统的调度了。操作系统给每个线程分配CPU时间片资源,然后不停的切换,从而实现“并行”执行的效果。

但是这样真的更快吗?从上面的例子可以看出,一个线程 就可以把一个核心 的利用率跑满。如果每个线程都很“霸道”,不停的执行指令,不给CPU空闲的时间,并且同时执行的线程数大于CPU的核心数,就会导致操作系统更频繁的执行切换线程执行 ,以确保每个线程都可以得到执行。

不过切换是有代价的,每次切换会伴随着寄存器数据更新,内存页表更新等操作 。虽然一次切换的代价和I/O操作比起来微不足道,但如果线程过多,线程切换的过于频繁,甚至在单位时间内切换的耗时已经大于程序执行的时间,就会导致CPU资源过多的浪费在上下文切换上,而不是在执行程序,得不偿失。

上面死循环空跑的例子,有点过于极端了,正常情况下不太可能有这种程序。

大多程序在运行时都会有一些 I/O操作,可能是读写文件,网络收发报文等,这些 I/O 操作在进行时时需要等待反馈的。比如网络读写时,需要等待报文发送或者接收到,在这个等待过程中,线程是等待状态,CPU没有工作。此时操作系统就会调度CPU去执行其他线程的指令,这样就完美利用了CPU这段空闲期,提高了CPU的利用率。

上面的例子中,程序不停的循环什么都不做,CPU要不停的执行指令,几乎没有啥空闲的时间。如果插入一段I/O操作呢,I/O 操作期间 CPU是空闲状态,CPU的利用率会怎么样呢?先看看单线程下的结果:

public class CPUUtilizationTest {
 public static void main(String[] args) throws InterruptedException {

  for (int n = 0; n < 1; n++) {
   new Thread(new Runnable() {
    @Override
    public void run() {
     while (true){
                        //每次空循环 1亿 次后,sleep 50ms,模拟 I/O等待、切换
      for (int i = 0; i < 100_000_000l; i++) {
      }
      try {
       Thread.sleep(50);
      }
      catch (InterruptedException e) {
       e.printStackTrace();
      }
     }
    }
   }).start();
  }
 }
}

 


哇,唯一有利用率的9号核心,利用率也才50%,和前面没有sleep的100%相比,已经低了一半了。现在把线程数调整到12个看看:

 


单个核心的利用率60左右,和刚才的单线程结果差距不大,还没有把CPU利用率跑满,现在将线程数增加到18:

 


此时单核心利用率,已经接近100%了。由此可见,当线程中有 I/O 等操作不占用CPU资源时,操作系统可以调度CPU可以同时执行更多的线程。

现在将I/O事件的频率调高看看呢,把循环次数减到一半,50_000_000,同样是18个线程:

 


此时每个核心的利用率,大概只有70%左右了。

线程数和CPU利用率的小总结

上面的例子,只是辅助,为了更好的理解线程数/程序行为/CPU状态的关系,来简单总结一下:

  • 一个极端的线程(不停执行“计算”型操作时),就可以把单个核心的利用率跑满,多核心CPU最多只能同时执行等于核心数的“极端”线程数
  • 如果每个线程都这么“极端”,且同时执行的线程数超过核心数,会导致不必要的切换,造成负载过高,只会让执行更慢
  • I/O 等暂停类操作时,CPU处于空闲状态,操作系统调度CPU执行其他线程,可以提高CPU利用率,同时执行更多的线程
  • I/O 事件的频率频率越高,或者等待/暂停时间越长,CPU的空闲时间也就更长,利用率越低,操作系统可以调度CPU执行更多的线程

线程数规划的公式

前面的铺垫,都是为了帮助理解,现在来看看书本上的定义。《Java 并发编程实战》介绍了一个线程数计算的公式:

Ncpu=CPU核心数Ncpu=CPU 核心数Ncpu=CPU核心数

Ucpu=目标CPU利用率,0<=Ucpu<=1Ucpu=目标CPU利用率,0<=Ucpu<=1Ucpu=目标CPU利用率,0<=Ucpu<=1

WC=等待时间和计算时间的比例\frac{W}{C}=等待时间和计算时间的比例CW=等待时间和计算时间的比例

如果希望程序跑到CPU的目标利用率,需要的线程数公式为:

Nthreads=Ncpu∗Ucpu∗(1+WC)Nthreads=NcpuUcpu(1+\frac{W}{C})Nthreads=Ncpu∗Ucpu∗(1+CW)

公式很清晰,现在来带入上面的例子试试看:

如果我期望目标利用率为90%(多核90),那么需要的线程数为:

核心数12 * 利用率0.9 * (1 + 50(sleep时间)/50(循环50_000_000耗时)) ≈ 22

现在把线程数调到22,看看结果:

 


现在CPU利用率大概80+,和预期比较接近了,由于线程数过多,还有些上下文切换的开销,再加上测试用例不够严谨,所以实际利用率低一些也正常。

把公式变个形,还可以通过线程数来计算CPU利用率:

Ucpu=NthreadsNcpu∗(1+WC)Ucpu=\frac{Nthreads}{Ncpu*(1+\frac{W}{C})}Ucpu=Ncpu∗(1+CW)Nthreads

线程数22 / (核心数12 * (1 + 50(sleep时间)/50(循环50_000_000耗时))) ≈ 0.9

虽然公式很好,但在真实的程序中,一般很难获得准确的等待时间和计算时间,因为程序很复杂,不只是“计算” 。一段代码中会有很多的内存读写,计算,I/O 等复合操作,精确的获取这两个指标很难,所以光靠公式计算线程数过于理想化。

真实程序中的线程数

那么在实际的程序中,或者说一些Java的业务系统中,线程数(线程池大小)规划多少合适呢?

先说结论:没有固定答案,先设定预期,比如我期望的CPU利用率在多少,负载在多少,GC频率多少之类的指标后,再通过测试不断的调整到一个合理的线程数

比如一个普通的,SpringBoot 为基础的业务系统,默认Tomcat容器+HikariCP连接池+G1回收器,如果此时项目中也需要一个业务场景的多线程(或者线程池)来异步/并行执行业务流程。

此时我按照上面的公式来规划线程数的话,误差一定会很大。因为此时这台主机上,已经有很多运行中的线程了,Tomcat有自己的线程池,HikariCP也有自己的后台线程,JVM也有一些编译的线程,连G1都有自己的后台线程。这些线程也是运行在当前进程、当前主机上的,也会占用CPU的资源。

所以受环境干扰下,单靠公式很难准确的规划线程数,一定要通过测试来验证。

流程一般是这样:

1.分析当前主机上,有没有其他进程干扰
2.分析当前JVM进程上,有没有其他运行中或可能运行的线程
3.设定目标

  • 目标CPU利用率 - 我最高能容忍我的CPU飙到多少?
  • 目标GC频率/暂停时间 - 多线程执行后,GC频率会增高,最大能容忍到什么频率,每次暂停时间多少?
  • 执行效率 - 比如批处理时,我单位时间内要开多少线程才能及时处理完毕
  • ……

4.梳理链路关键点,是否有卡脖子的点,因为如果线程数过多,链路上某些节点资源有限可能会导致大量的线程在等待资源(比如三方接口限流,连接池数量有限,中间件压力过大无法支撑等)
5.不断的增加/减少线程数来测试,按最高的要求去测试,最终获得一个“满足要求”的线程数**

而且而且而且!不同场景下的线程数理念也有所不同:

1.Tomcat中的maxThreads,在Blocking I/O和No-Blocking I/O下就不一样
2.Dubbo 默认还是单连接呢,也有I/O线程(池)和业务线程(池)的区分,I/O线程一般不是瓶颈,所以不必太多,但业务线程很容易称为瓶颈
3.Redis 6.0以后也是多线程了,不过它只是I/O 多线程,“业务”处理还是单线程

所以,不要纠结设置多少线程了。没有标准答案,一定要结合场景,带着目标,通过测试去找到一个最合适的线程数。

可能还有同学可能会有疑问:“我们系统也没啥压力,不需要那么合适的线程数,只是一个简单的异步场景,不影响系统其他功能就可以”

很正常,很多的内部业务系统,并不需要啥性能,稳定好用符合需求就可以了。那么我的推荐的线程数是:CPU核心数

附录

Java 获取CPU核心数

Runtime.getRuntime().availableProcessors()//获取逻辑核心数,如6核心12线程,那么返回的是12

Linux 获取CPU核心数

# 总核数 = 物理CPU个数 X 每颗物理CPU的核数
# 总逻辑CPU数 = 物理CPU个数 X 每颗物理CPU的核数 X 超线程数

# 查看物理CPU个数
cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l

# 查看每个物理CPU中core的个数(即核数)
cat /proc/cpuinfo| grep "cpu cores"| uniq

# 查看逻辑CPU的个数
cat /proc/cpuinfo| grep "processor"| wc -l

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

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

相关文章

水资源税取水计量监管系统 取用水户水量在线监测平台 水资源远程实时监控管理系统

平升电子水资源税取水计量监管系统/取用水户水量在线监测平台/水资源远程实时监控管理系统适用于水资源管理部门对地下水和地表水的用水量、水位、水质进行监测&#xff0c;还可扩展远程或自动控制泵/闸/阀实现用水量控制。系统帮助管理部门掌握所辖区域内水资源取用水情况&…

打电话用蓝牙耳机什么牌子好?打电话清晰的蓝牙耳机推荐

随着蓝牙耳机的普及&#xff0c;我们可以享受到沉浸式的音乐。在不打扰任何人的情况下&#xff0c;尽情的享受&#xff0c;使用蓝牙耳机有时候避免不了来电&#xff0c;为了保证通话的清晰&#xff0c;许多人在选购的时候也会更加的看重麦克风&#xff0c;下面小编整理了几款打…

如何使用JMeter操作Elasticsearch

JMeter是Apache组织基于Java开发的压力测试工具&#xff0c;用于对软件做压力测试&#xff0c;Elasticsearch是一个分布式、高扩展、高实时的搜索与数据分析引擎(简称ES)&#xff0c;下面来展示最基本的用JMeter操作ES示例。 打开JMeter工具&#xff0c;在测试计划下添加“线程…

【金万维】使用天联高级版登录U8,进行凭证打印操作。

【操作步骤】 通过“天联高级版客户端”登录 U8&#xff0c;打印凭证步骤&#xff1a; 第一步&#xff1a;首先查看一下天联高级版客户端的打印参数是否如下图所示。 &#xff08;一般软件初次安装后&#xff0c;默认即可。&#xff09; 第二步&#xff1a;进入U8后&#xff0…

web概述20

MVC模式 MVC全名是Model View Controller是模型视图控制器的缩写&#xff0c;是一种软件设计典范&#xff0c;是一种架构型的模式&#xff0c;本身不引入新功能&#xff0c;只是帮助将开发的结构组织的更加合理。 它使用一种业务逻辑、数据、界面显示分离的方法&#xff0c;将…

麦芽糖-聚乙二醇-顺铂 cisplatin-PEG-maltose

麦芽糖-聚乙二醇-顺铂 cisplatin-PEG-maltose 中文名称&#xff1a;麦芽糖-顺铂 英文名称&#xff1a;maltose-cisplatin 别称&#xff1a;生物素修饰麦芽糖 生物素-麦芽糖 麦芽糖-聚乙二醇-顺铂 cisplatin-PEG-maltose 顺铂-PEG-麦芽糖 纯度&#xff1a;95% 存储条件…

电动车充电费到了涨价的时候了,低能源使用成本正在成为过去

电动汽车以省钱成为各个新能源汽车企业吹嘘的宣传点&#xff0c;然而电动汽车车主如今正面临公共充电桩短缺的问题&#xff0c;公共充电桩的建设跟不上电动汽车增长的速度&#xff0c;导致电动汽车车主充电难问题日益突出&#xff0c;解决这个问题就只能通过涨价来解决供应短缺…

ln命令应用

记录&#xff1a;352 场景&#xff1a;在CentOS 7.9操作系统上&#xff0c;使用ln命令创建软链接(symbolic links)和硬链接(hard links)。解决&#xff1a;Too many levels of symbolic links。 版本&#xff1a; 操作系统&#xff1a;CentOS 7.9 1.命令应用 (1)目录创建软…

mybatis-plus,sgg,杨bochao,p5完成

一 MyBatis-Plus简介 增强工具。只做增强不做改。 可以直接在mybatis的基础上整合mybatis-plus。此时并不会影响mybatis的功能&#xff0c;即mybatis原来的功能都在&#xff0c;该怎么用还怎么用。锦上添花的是还能使用mybatis-plus提供的&#xff1a;通用的mapper、通用的ser…

drone+github实现自动化部署

目录drone简介drone与jenkins对比创建oauth2服务器安装drone生成drone的共享密钥配置drone.yml文件安装drone项目配置配置Dockefile配置.drone.yml文件配置阿里云镜像拉取文件drone简介 drone与jenkins对比 创建oauth2 在github中设置第三方app配置。 需要记住id…

[附源码]Python计算机毕业设计Django车源后台管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

微信截图无法发送,也发不出电脑上的图片

微信截图无法发送&#xff0c;也发不出电脑上的图片 现象 今天微信突然出现这个问题&#xff0c;怎么改设置都调不好&#xff0c;卸载重装都不行&#xff0c;最后发现&#xff0c;微信的消息目录中&#xff0c;一些文件无法删除&#xff0c;提示“文件或目录损坏且无法读取”…

上市公司排污费2010-2020重污染行业环境披露水平-原始数据及计算结果

一、重污染行业环境信息披露水平 1、数据来源&#xff1a;见附件 2、时间跨度&#xff1a;2009-2020 3、区域范围&#xff1a;重污染行业的认定主要依据环境保护部2008年6月发布的《上市公司环保核查行业分类管理名录》&#xff08;环办函[2008]373号&#xff09;、《上市公…

PDF转TXT怎么转?看完这篇你就会了

我们在学习工作的时候&#xff0c;经常会使用PDF格式来传输文件&#xff0c;因为PDF具有较强的格式稳定性。可是有时候我们也需要将PDF转成TXT格式&#xff0c;因为有些电子书阅读器比较适合阅读TXT格式的文件。那你们知道PDF怎么转TXT吗&#xff1f;有需要转换PDF格式的小伙伴…

Apollo 应用与源码分析:Monitor监控-软件监控-进程存活监控-process_monitor

目录 流程 代码 分析 获取可以运行的进程的信息 检查HMI 的模块信息 检查被监控的组件 检查其他组件 判断进程状态UpdateStatus 流程 代码 class ProcessMonitor : public RecurrentRunner {public:ProcessMonitor();void RunOnce(const double current_time) overrid…

BUUCTF Misc 被劫持的神秘礼物 刷新过的图片 [BJDCTF2020]认真你就输了 [BJDCTF2020]藏藏藏

被劫持的神秘礼物 下载文件 提示让我们找账号密码 wireshark打开上述文件 可以发现一个POST请求登录接口的HTTP包&#xff0c;追踪http流 数据包中可以发现用户名&#xff1a;admina 密码&#xff1a;adminb 打开md5在线加密 得到flag flag{1d240aafe2…

第二证券|新能源优势突出 青海加速储能产业布局

大唐青海动力开发有限公司工作人员在青海省海南藏族自治州共和县塔拉滩光伏电站巡检。 无论是新动力场站直流侧的储能技能应用&#xff0c;还是同享储能形式试点&#xff0c;近年来&#xff0c;青海储能职业迎来跨越式展开新阶段。业内遍及看好未来储能展开&#xff0c;作为全…

多线程(2)

文章目录前言 &#xff1a;1.Thread类 &#xff1a;1.1 Thread类常见的构造方法1.2 Thread的几个常见属性1.3 中断一个线程1.4 等待一个线程-join()1.5 获取当前线程引用1.6 休眠当前线程2.线程状态前言 &#xff1a; 简单回顾上文知识点 上文我们了解了 线程是为解决并发编程引…

Linux网络编程——IO多路复用

文章目录1&#xff0c;I/O模型2&#xff0c;阻塞I/O 模式2.1&#xff0c;读阻塞&#xff08;以read函数为例&#xff09;2.2&#xff0c;写阻塞3&#xff0c;非阻塞I/O模式3.1&#xff0c;非阻塞I/O模式的实现&#xff08;fcntl()函数、ioctl() 函数&#xff09;3.1.1&#xff…

Apollo 应用与源码分析:Monitor监控-软件监控-channel时间延迟监控

目录 代码 分析 主要结构 判断逻辑 备注 代码 class ChannelMonitor : public RecurrentRunner {public:explicit ChannelMonitor(const std::shared_ptr<LatencyMonitor>& latency_monitor);void RunOnce(const double current_time) override;private:static …