CPU密集型和IO密集型对 CPU内核之间的关系

概览

CPU密集型与I/O密集型是在计算机上执行任务的两种策略,在并发执行任务场景下,我们需要选择使用多线程或多进程;
如果是I/O密集型任务,使用多线程,线程越多越好;
如果是CPU密集型任务,使用多进程,线程数量与CPU核心数匹配。

我们了解这些概念有助于在资源分配和性能优化等方面有很大的帮助。
我们在选择线程池的时候,我们需要知道某一个任务是否是CPU消耗型的任务,还是说I/O类型的任务,以便充分的调用CPU资源。

CPU密集型

CPU密集型,也叫计算密集型。
系统运行时,CPU读写I/O(硬盘/内存)时可以在很短的时间内完成,几乎没有阻塞时间(等待I/O的实时间),而CPU一直有大量运算要处理,因此CPU负载长期过高。

CPU密集几乎无I/O阻塞,CPU一直会全速运行。如果是单核情况下,开多线程是没有意义的,一个CPU来回切着运行,增加线程切换的资源消耗。
可见,CPU密集任务只有在多核CPU上、开多线程才可能提速。

CPU使用率较高时(如我们训练算法模型、搞训练集),通常线程数只需要设置为CPU核心数的线程个数就可以了。

$$
一般其计算公式可遵循:CPU密集型核心线程数 = CPU核数 + 1。

$$

《Java并发编程实践》这么说:计算密集型的线程恰好在某时因为发生一个页错误或者因其他原因而暂停,刚好有一个“额外”的线程,可以确保在这种情况下CPU周期不会中断工作。

特点:

进行大量的计算
消耗CPU资源,较高的CPU占用率,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。
较少的IO操作

I/O密集型

I/O密集型相反,听名字就知道,系统运行多是CPU在等I/O (硬盘/内存) 的读写操作,此类情景下CPU负载并不高。

I/O密集型的程序一般在达到性能极限时,CPU占用率仍然较低。
这可能是因为任务本身需要大量I/O操作,没有充分利用CPU能力,导致线程空余时间很多。
通常我们会开CPU核心数数倍的线程,在线程进行 I/O 操作 CPU 空闲时,启用其他线程继续使用 CPU,以提高 CPU 的使用率,充分利用CPU资源。
一般其计算公式可遵循: I / O 密集型核心线程数 = (线程等待时间 / 线程 C P U 时间 + 1 ) ∗ C P U 数目。 一般其计算公式可遵循:I/O密集型核心线程数 = (线程等待时间/ 线程CPU时间 + 1)* CPU数目。 一般其计算公式可遵循:I/O密集型核心线程数=(线程等待时间/线程CPU时间+1CPU数目。
当然我们也看到有多种计算公式,但都不是最优解,具体情况需结合项目实际使用,配置合适的线程数
一般来说:文件读写、DB读写、网络请求等都是I/O密集型

特点:

高IO操作
计算操作少
CPU占用率低

如何区分IO密集型、CPU密集型任务

我们需要知道某一个任务是否是CPU消耗型的任务(定容线程池),还是说I/O类型的任务(缓存线程池),充分的调用CPU资源。

那在此之前,我们需要知道两个概念:

Wall Duration:代码执行时间(包括了running + runnable + sleep等所有时长)

比如我们要知道某方法执行时间,可以通过系统时间差即可:

void method() {
    long start = System.currentTimeMillis();
    // 业务代码    
    long wallTime = System.currentTimeMillis() - start;
}

CPU Duration: 代码消耗CUP的时间(重点指标,优化方向)。

void method() {
    long start = SystemClock.currentThreadTimeMillis(); //当前线程运行了多少时间(毫秒值,不含thread或systemclock.sleep的值)
    // 业务代码    
    long wallTime = SystemClock.currentThreadTimeMillis() - start;
}

消耗的CPU时间片较多,我们就把它定义为CPU消耗型的任务,放在定容线程池里调度(即线程数量固定)

消耗的时间片少,我们就把它定义为IO类型的任务,放在缓存线程池中。

缓存线程池(CachedThreadPool)是Java中的一种线程池类型。它是一种动态线程池,可以根据需要自动创建新的线程,并在线程空闲一段时间后销毁。

如何合理设置核心线程数

对于设置CPU密集型和I/O密集型任务的核心线程数,有一些一般的建议和策略,但具体的最佳设置取决于你的应用程序和系统环境的特性。以下是一些建议:

  1. CPU密集型任务:
    • 通常情况下,将线程数设置为可用CPU内核的数量是一个不错的选择,以充分利用多核CPU的并行计算能力。
    • 可以通过 Runtime.getRuntime().availableProcessors() 方法获取当前系统可用的CPU内核数量,然后将该数量作为核心线程数进行设置。
    • 注意,过多的线程数可能会导致线程上下文切换的开销增加,因此确保线程数不超过系统所能承受的范围。
  2. IO密集型任务:
    • 系统运行多是CPU在等I/O (硬盘/内存) 的读写操作,此类情景下CPU负载并不高。I/O密集型的程序一般在达到性能极限时,CPU占用率仍然较低,所以通常就需要开CPU核心数两倍的线程。当线程进行 I/O 操作 CPU 空闲时,启用其他线程继续使用 CPU,以提高 CPU 的使用率。例如:数据库交互,文件上传下载,网络传输等。
    • 增加线程数可以充分利用CPU在等待IO操作时的空闲时间,提高整体的效率。
    • 但是,过多的线程数也可能会导致系统资源的浪费和线程调度的开销增加,因此需要适度平衡。

除了设置核心线程数外,还有其他一些方法可以提高CPU和IO密集型任务的利用率和效率:

  1. 使用线程池:使用线程池可以更好地管理和复用线程资源,避免频繁地创建和销毁线程。
  2. 任务分解和并行处理:对于大型的任务,将其分解成更小的子任务,并进行并行处理,以提高整体的效率。
  3. 异步IO操作:对于IO密集型任务,可以考虑使用异步IO操作,以减少线程等待IO的时间,提高系统的吞吐量。

总结来说,CPU密集型任务更适合利用多核CPU的并行计算能力,而IO密集型任务则可能受限于IO操作的速度,无法充分利用多个CPU内核。在实际开发中,根据任务的特点选择合适的并行策略和资源分配方式,以优化系统的性能。

参考链接:CPU密集型和IO密集型任务的权衡:如何找到最佳平衡点

什么是CPU密集型?什么是IO密集型?

你可能感兴趣的:(java)