线程池大小选择:针对 I/O 密集型场景和 CPU 密集型场景

I/O 密集型场景

I/O 密集型场景指的是系统的磁盘以及内存的性能要高于CPU 性能,因此系统在这种场景下,大部分时间消耗在磁盘/内存的读写,CPU 的利用率不高。比如一些包含网络传输的场景。通常我们不会希望 CPU 成为瓶颈,因为 CPU 毕竟是计算机中很昂贵的部分,我们希望 CPU 可以做更多的工作,因为我们会通过调整线程池大小来转移业务瓶颈,这将在下文提到。

CPU密集型场景

CPU 密集型则相反,指的是 CPU 的性能高于磁盘和内存,这时 CPU 的利用率为 100%,I/O 只需要很短的时间就可以完成。比如当使用 CPU 时(而不是 GPU),神经网络的前向和反向计算就是典型的 CPU 密集型场景,因为这其中涉及了大量的卷积和数值运算。

线程池大小选择

线程池在 I/O 密集型场景的必要性

在这两种场景下,线程池大小的选择也是不同的。在 I/O 密集型场景中,我们需要增大线程池的大小以通过 overlap 消除 CPU 等待 I/O 的时间。这样讲很抽象,可以参考下图,首先我们假设这是一个任务,红色部分表示 I/O 时间,白色部分表示 CPU 处理时间
单个任务
当线程池大小为1,也就是串行执行时,可以看到中间会有很多等待 I/O 的时间,CPU 被白白浪费:
单线程
当线程池大小为2,两个线程并发执行,这时候可以遮盖(overlap)I/O 的部分,从外部看 CPU 一直在执行,没有干等的时候:
多线程
可以看到合理设置线程池大小,可以将 I/O 的时间给遮盖掉,更好地利用 CPU 的资源。具体公式将在后续讲解。

线程池在 CPU 密集型场景的必要性

实际上,只有多核 CPU 的主机才适合使用线程池处理 CPU 密集型场景。我们举个例子,在单核 CPU 中,CPU 占用率为 100%,这时我们的线程池大小设置为2,那么在线程切换时需要有线程上下文切换的开销,这是需要时间的,而且线程的切换并没有意义,单个 CPU 的的利用率一直是 100%,相当于白白多了上下文切换的时间。而假如是 6 核的 CPU,这时可以将线程池大小设置为 6,这样 6 个 CPU 中都有自己的线程,处理速度可以提升 6 倍。

线程池大小计算

铺垫了前面的理论,我们这里具体给出线程池大小的计算公式。首先明确几个变量:

  1. Ncpu = CPU 的数量
  2. Ucpu = CPU 的目标使用率
  3. W / C = 等待 I/O 的时间和 CPU 计算时间的比
    为保持 CPU 的使用率,最优的线程池大小为:
    Nthreads = Ncpu x Ucpu x (1 + W / C)
    这个公式的由来我们可以参考《Linux 多线程服务器端编程》中的一个思路:
    如果线程池在执行任务时,CPU 计算所占的时间比重为 P,主机一共有 C 个 CPU,为了让 C 个 CPU 都跑到 100%,那么我们需要 C / P 个线程,所以也就是:
    Nthreads =(( I/O 时间 + CPU 计算时间 )/ CPU 计算时间)x CPU 数量
    也就是
    Nthreads = (( I/O 时间 / CPU 计算时间 )+ 1)x CPU 数量
    再加上目标的 CPU 利用率,就是上面提到的公式了。

所以我们可以分析在不同场景下的线程池大小计算,I/O 密集型场景,根据公式计算即可。CPU 密集型场景,根据公式应该是 CPU 核数,但是实际应用场景中通常用 CPU 核数 + 1,这个 + 1 实际上是为了保证当某个 CPU 上的线程因为缺页中断或某些原因暂停时,刚好有一个额外的线程来保证 CPU 不会空等,总之还是让 CPU 一直处于忙碌的状态。

Linux 下查看 CPU 利用率和 I/O 占用率

CPU 利用率查看

1 - 使用 top 命令:

在这里插入图片描述

关注 %Cpu(s) 的第四项 id,表示的是空闲 CPU 的百分比,所以在我这个场景下,CPU 的利用率就是 1 - 74.3 % = 25.7%,如果该主机拥有多个 CPU,可以在 top 命令界面按1,会显示多个 CPU 的情况,如下图所示:

在这里插入图片描述

所以在 top 命令查看时,如果发现 CPU 空闲率接近 0,此时的任务就是 CPU 密集型场景了。

2 - 使用 vmstat -w -w 命令:

在这里插入图片描述

如图,看最右边 CPU 一项,同样可以查看当前 CPU 的空闲率,在这里是 97%,也就是 CPU 利用率为 3%。

I/O 查看

1 - 使用 top 命令:

在这里插入图片描述

我们同样可以使用 top 命令查看 I/O 是否频繁,这里关注 CPU 项的 wa,wa 表示等待输入输出(也就是 I/O)的时间百分比,这一项如果高于 30%,我们可以认定为存在 I/O 瓶颈,也就是当前为 I/O 密集型任务。

2 - 使用 vmstat 命令:

在这里插入图片描述

查看其中的 io 项,其中 bi 表示发送到块设备的块数,单位为块/秒,bo 表示从块设备接收到的块数,单位为块/秒。

3 - 使用 iostat 命令:
输入 iostat -x 5,其中 -x 表示更详细信息

在这里插入图片描述

查看最后的 util 项表示的是采用周期内用于 I/O 操作的时间比率,即IO队列非空的时间比率,可以用于表示 I/O 繁忙程度。如果 %util 接近 100%,说明产生的 I/O 请求太多,I/O 系统已经满负荷,该磁盘可能存在瓶颈。

总结

总的来说,线程 I/O 时间比例越高,需要越多线程来 overlap 这个 I/O,反之需要越少的线程。
文末介绍了在 Linux 系统中如何查看 CPU 利用率和 I/O 繁忙程度。
同时需要认识到,并不是使用线程池就一定比单线程高效,单线程相比多线程避免了上下文切换时间和锁。因此也可以很高效。
有了以上方法,我们就可以做到在不同业务场景中选择不同的线程池大小了。

猜你喜欢

转载自blog.csdn.net/weixin_41670608/article/details/126494729
今日推荐