首先需要分析2个问题:
- 为什么要使用多线程
- 多线程的应用场景
为什么要使用多线程
一个字,快
。为了提升性能
。那么性能该如何用理性的数字而非感性的认知来量化呢?性能有两个核心指标:延迟
和吞吐量
。
延迟
:发出请求到收到响应需要的时间,延迟越小,单个请求的执行速度越快,性能越好。
吞吐量
:单位时间内能处理的请求数量,吞吐量越大,程序处理的请求越多,性能越好。
这两个指标内部有一定的联系:同等条件下,延迟越低则吞吐量越大,但是这两个指标一个属于时间维度一个属于空间维度,不能互相转换。
提升性能则是从降低延迟``,提高吞吐量
着手,这也是我们使用多线程
的主要目的。
多线程的应用场景
提升性能的两个方向:
- 优化算法
- 提高硬件利用率
操作系统已经姐姐了硬件利用率:操作系统已经解决了磁盘和网卡利用率问题,利用中断机制可以避免 CPU
轮询 I/O
状态,提升了 CPU
利用率。但是操作系统解决硬件利用率问题的对象往往是单一的硬件设备,而我们编写的 Java
程序往往需要 CPU
和 I/O
设备互相配合工作。
所以我们需要解决的是 CPU
和 I/O
设备综合利用率的问题,这个问题的解决方法就是使用 多线程
。
如下图所示,如果只有一个线程,则执行I/O 操作的时候 CPU 是闲置的,执行 CPU 操作的时候 I/O 设备是闲置的,所以 CPU
和 I/O
设备的利用
率是 50%
。
单线程执行示意图
如果有两个相乘,就可以在 CPU 计算时 I/O 设备同时读写数据,这样 CPU 和 I/O 设备的利用率就达到了 100%,时刻都有线程使用这两个硬件设备。
双线程执行示意图
而从单线程
到两个线程
的直接性能表现就是 单位时间处理的请求数量翻倍,吞吐量
提高一倍,所以如果我们发现 CPU
和 I/O
设备的利用率都很低,则可以通过增加线程数量
来提高吞吐量
。
以上针对的是单核时代
,多线程的主要作用就是用来 平衡 CPU
和 I/O
设备之间的速度差异。但是如果程序是 CPU 密集型而不存在 I/O 操作的话,多线程反而会使性能变差,原因是 CPU 本身就一直满载了,而线程之间切换是存在开销的,相当于增加了额外的开销。
但是在 多核时代,每个 CPU 核心上都可以运行一个线程,所以即便是 CPU 密集型的程序,使用多线程也可以提升性能:
多核执行多线程示意图
举个简单的例子:
比如要计算 1-100亿的数的和是多少,在4核环境下,可以分配给4个线程:线程1 计算 1-25亿 线程2计算 25 - 50亿,线程3 计算 50 -75亿,线程4计算 75 -100亿,这就比使用单个线程要快接近4倍。
创建多少线程合适
分为两个场景,CPU密集型和I/O密集型。
-
CPU密集型
:多线程本质上是提高
多核CPU
的利用率,所以对于4核
CPU 来说,每个核一个线程,理论上创建4个
就可以了,再多的话反而可能增加线程切换的成本。但是在工程
上线程的数量一般会设置为CPU 核数+1
这样的话当线程偶尔因为内存页失效
或其他原因
导致阻塞
时,这个额外的线程可以补上,保证 CPU 利用率。 -
I/O 密集型
:如果 CPU 计算 和 I/O 操作耗时1:1
,那么2个
是最合适的。 如果 CPU 操作和 I/O 操作耗时1:2
,则三个线程
最合适,如下图所示:CPU 在A
、B
、C
三个线程之间切换,对于线程 A
来说,当 CPU 从B
、C
切换回来时,线程A
正好执行完I/O
操作。 这样CPU
和I/O
的利用率都达到了100%
。
三线程执行示意图
对于 I/O 密集型场景,最佳线程数与程序中 CPU 计算和 I/O 操作的耗时比相关,可以总结出如下公式:
$$
单核最佳线程数 = 1+(\frac{I/O耗时}{CPU耗时})
$$
令:
$$
R = \frac{I/O耗时}{CPU耗时}
$$
配合上图可以这样理解:当线程A
执行 I/O
操作时, 另有 R
个线程正好执行完各自的 CPU
计算,这样 CPU 的利用率就达到了 100%
。
上面针对的是单核
,如果是多核
则等比扩大
:
$$
多核最佳线程数 = CPU核心数量 * [1+(\frac{I/O耗时}{CPU耗时})]
$$
个人总结:
这章的收获主要是针对不同场景
该怎样设置线程数量的一个思路,我认为学知识首先针对场景
,也就是需求,然后才有不同的配置,而不是去学一个固定的配置方法。
我们设置线程数值需要把握最大化硬件性能就可以,而这个前提就是我们将场景分析的比较清晰,是 I/O 密集型
还是 CPU 密集型
,所以在进行压力测试的时候需要重点关注 CPU、I/O 设备的利用率和性能指标。
Q.E.D.
Comments | 0 条评论