线程池大小选择:针对 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 计算时间) cpu 数量*
    也就是
    Nthreads = (( I/O 时间 / cpu 计算时间 )+ 1) cpu 数量*
    加上目标的 cpu 利用率,就是上面提到的公式了。

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

总结

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

相关文章

显卡天梯图2024最新版,显卡是电脑进行图形处理的重要设备,...
初始化电脑时出现问题怎么办,可以使用win系统的安装介质,连...
todesk远程开机怎么设置,两台电脑要在同一局域网内,然后需...
油猴谷歌插件怎么安装,可以通过谷歌应用商店进行安装,需要...
虚拟内存这个名词想必很多人都听说过,我们在使用电脑的时候...