天天用线程池,线程数究竟设置多少才是合理的?
在日常工作中,经常遇到凭感觉、凭经验来设置线程池的线程数,但这背后实际隐藏着深刻的计算机原理。
1. 1 + N + M的线程模型
在讨论线程数之前,先说明一个最典型的服务器程序的线程模型,无论是Java(Tomcat,Netty),还是C++,通常都是这个套路。
1:1个监听线程,监听新来的socket
N:N个IO线程,负责对socket进行读写。N一般等于cpu核数
M:业务逻辑处理线程。
这里所说的线程数,就是指M应该多大合适?
2. CPU密集 vs. IO密集
线程数的设置反映了一个基本的计算机原理:你的应用是cpu密集型 vs. IO密集型?
极端情况,没有任何IO的情况下,每个核1个线程就够了,有多少个核,就有多少个线程。
更进一步,因为没有后端的IO,可以把1 + N + M模型合并成一个线程,这个线程既负责网络入口的IO,也负责业务逻辑处理,最典型的就是Redis。
但实际中,我们的程序往往都有网络IO,比如访问DB,访问Redis,访问Kafka。
假设CPU: IO占比是3:7,那线程数 = cpu核数 * 7/3。也就是:IO越多,CPU会越闲。要想充分利用CPU,就需要开更多的线程。
问题在于:我们如何精确知道CPU: IO的比例是多少?1:9, 3:7, 还是4:6?
3. 如何精确测算CPU: IO比
这个比例是由代码逻辑决定的,没有办法凭感觉去猜测。一个测算方法就是对代码的耗时进行打点,统计代码的执行耗时。看一个请求处理的总时间里面,CPU计算占了多少时间,IO占了多少时间。
但实际场景比这个更复杂,一个复杂的系统肯定不止一个接口,每个接口业务逻辑不一样,CPU:IO比例也不一样。在这种情况下,怎么测算呢?
假设通过代码打点,知道了:
接口1:cpu:io = 1:9
接口2: cpu:io = 3:7
接口3: cpu:io = 2:8
接口4: cpu:io = 1:9
最终,整个系统的cpu:io比例是多少呢?所有接口的cpu:io比求平均值?
显然不是。对于一个系统来说,每个接口的调用量并不是一样的,我们可以通过监控系统查看历史调用量的统计数据,得到每个接口的调用量占比,系统的cpu:io比是所有接口的加权平均。即:
整个系统的cpu: io = 接口1的调用量占比 * 接口1的cpu:io比 + 接口2的调用量占比 * 接口2的cpu:io比 + … + 接口N的调用量占比 * 接口N的cpu:io比
最终,知道了这个比例,就能合理的设置线程数了。