Linux中对CPU加压的思考
问题引出
有个需求,要对服务打流量,从而让CPU占用达到峰值,进而查看系统的状态。但是由于基础设施的原因,只能通过ab
打流量,因此我能打的流量的受限于客户机的带宽。这个时候怎么加ab
的并发量和请求数,也只能让CPU的占用达到一个阈值。这个时候自然就想到直接上服务器,给它的CPU加压,达到加压测试的效果。
问题描述
那该如何加压?Linux上自然有工具的吧。
在 Linux 上给 CPU 加压通常是为了测试系统的性能、稳定性或者进行基准测试。你可以通过多种方法让 CPU 占用率提高,从而进行负载测试。以下是一些常用的方法:
1. 使用 stress
工具
stress
是一个用于对系统进行负载测试的工具,可以使 CPU、内存、I/O 等资源都达到高负载。
使用 stress
给 CPU 加压
1 | stress --cpu 4 --timeout 60 |
--cpu 4
:表示使用 4 个 CPU 线程进行压力测试。--timeout 60
:表示测试持续 60 秒。
2. 使用 stress-ng
工具
stress-ng
是 stress
的一个扩展版本,功能更为强大。
使用 stress-ng
给 CPU 加压
1 | stress-ng --cpu 4 --timeout 60s |
--cpu 4
:表示使用 4 个 CPU 线程。--timeout 60s
:表示测试持续 60 秒。
3. 使用 yes
命令
yes
命令会持续输出一个字符串(默认为 y
),从而让 CPU 处于高负载状态。
1 | yes > /dev/null & |
- 这个命令会在后台运行
yes
,并将输出重定向到/dev/null
,以防止输出过多的数据。
你可以运行多个 yes
命令来进一步增加负载,例如:
1 | yes > /dev/null & |
4. 使用 dd
命令
dd
命令可以用来进行 I/O 操作,通过生成大量的数据来使 CPU 处于高负载状态。
1 | dd if=/dev/zero of=/dev/null bs=1M count=1000 & |
if=/dev/zero
:指定输入文件。of=/dev/null
:指定输出文件。bs=1M
:块大小为 1MB。count=1000
:总共读取 1000 块数据。
5. 使用 Python 脚本
你可以使用 Python 脚本来生成 CPU 负载。
1 | #!/usr/bin/env python3 |
保存为 cpu_load.py
,然后运行:
1 | python3 cpu_load.py |
6. 使用 taskset
结合自定义负载脚本
taskset
可以将进程绑定到特定的 CPU 核心。结合自定义负载脚本,你可以实现更加精确的负载测试。
使用 taskset
运行 yes
命令:
1 | taskset -c 0 yes > /dev/null & |
-c 0
和-c 1
:将yes
命令绑定到 CPU 核心 0 和 1。
其实大概方法就是让CPU干点计算量,从而增加占用。
由于环境问题,我的服务器上安装软件包不是很方便,于是最方便的反而是写一个脚本来增压,也就是上面的:
1 | #!/usr/bin/env python3 |
直接运行这个脚本,结果却让我很吃惊:
CPU的占用直接达到了100%
这种其实有点和我的经验不一致啊:CPU占用是由于CPU有计算任务要去干,而我这个脚本就是个空循环,压根没有去干什么事情啊。同时在我的经验中,要是CPU占用满了,电脑自然会卡顿。
问题分析
那么回到最初的问题,CPU占用到底和什么有关系?
CPU 的占用率受多种因素影响,包括系统负载、进程行为、硬件性能等。以下是一些主要的因素,这些因素可以影响 CPU 的占用率:
1. 运行的进程和线程
- 进程数量:系统中运行的进程数量会影响 CPU 占用。如果有大量进程同时运行,尤其是那些 CPU 密集型的进程,会导致 CPU 占用率增加。
- 线程数:多线程应用程序可能会在多个 CPU 核心上同时执行,这可能会增加 CPU 占用率。
2. 进程类型
- CPU 密集型进程:这些进程主要使用 CPU 资源进行计算,如数据处理、计算密集型任务等。它们通常会导致较高的 CPU 占用率。
- I/O 密集型进程:这些进程主要进行输入/输出操作,如文件读写、网络传输等。虽然这些进程主要消耗 I/O 资源,但在等待 I/O 操作完成时也可能会间接增加 CPU 占用。
3. 系统负载
- 负载平均值:负载平均值是系统负载的一个指标,它表示在过去 1、5、15 分钟内,平均有多少个进程正在等待 CPU 时间。较高的负载平均值通常意味着系统过载,可能会导致 CPU 占用率高。
4. 应用程序行为
- 优化和效率:某些应用程序可能没有优化好,导致 CPU 占用率高。例如,死循环、内存泄漏或不必要的计算可能导致 CPU 过度使用。
- 优先级和调度:应用程序的优先级和调度策略也会影响 CPU 占用率。高优先级的进程可能会获得更多的 CPU 时间。
5. 系统配置
- CPU 核心数:系统中 CPU 核心的数量会影响 CPU 占用率。单核 CPU 的系统通常比多核 CPU 的系统更容易出现高占用情况,因为所有任务都需要在一个核心上处理。
- 超线程技术:启用超线程技术的 CPU 允许每个物理核心同时处理多个线程,这可以提高并行处理能力,影响 CPU 占用情况。
6. 系统资源
- 内存:系统内存不足可能导致频繁的交换(swap),从而增加 CPU 的负担。频繁的交换会导致 CPU 花费更多时间处理内存操作。
- 磁盘 I/O:磁盘读写操作的高负载可能导致 CPU 处理 I/O 请求的时间增加,间接影响 CPU 占用率。
7. 后台任务和服务
- 系统守护进程和服务:后台运行的守护进程和服务也可能占用 CPU 资源。例如,数据库服务、日志记录、监控工具等可能会导致 CPU 占用率增加。
- 定时任务:计划任务(如
cron
任务)或定期运行的脚本也可能在特定时间段内增加 CPU 占用。
8. 系统和应用程序的更新
- 软件更新:系统和应用程序的更新可能会改变它们的资源使用模式,新的功能或修复可能会影响 CPU 占用率。
- 驱动程序:硬件驱动程序的更新或更改也可能影响 CPU 的使用情况。
监控和诊断工具
- **
top
或htop
**:可以实时查看 CPU 占用情况和各进程的 CPU 使用情况。 - **
ps
**:可以查看当前运行的进程及其 CPU 占用情况。 - **
sar
**:可以提供详细的系统性能数据,包括 CPU 使用情况。 - **
iotop
**:可以监控 I/O 操作,帮助确定 I/O 密集型进程对 CPU 的影响。
总结
CPU 的占用率与系统中的多种因素相关,包括运行的进程和线程、进程类型、系统负载、应用程序行为、系统配置、内存和磁盘 I/O、后台任务以及系统和应用程序的更新。通过监控这些因素,你可以更好地理解和管理 CPU 的使用情况,优化系统性能。
根据上面说的第四点,是否存在说法:死循环导致CPU占用飙升?
那么尝试改一下死循环验证一下:
1 | #!/usr/bin/env python3 |
再看一眼占用情况:
直接降低到0了!也就是说,**无限空循环会造成CPU占用达到100%**!
那么更加糊涂了,空循环什么事情都没干,理论上占用更低才对啊。
分析一下:
原因分析
- 持续运行:
- 空循环没有任何退出条件,会一直运行下去。
- 由于没有任何休眠或阻塞操作,循环在每个时钟周期都会执行一次迭代。
- 无I/O操作:
- 循环没有进行任何I/O操作(如文件读写、网络通信等),这些操作通常会导致进程进入阻塞状态,从而释放CPU资源给其他进程。
- 因此,空循环不会等待任何外部事件,始终处于运行状态。
- 无休眠或等待:
- 空循环没有使用任何形式的休眠或等待指令(如
sleep
)。 - 因此,CPU没有任何时间可以进入空闲状态。
- 空循环没有使用任何形式的休眠或等待指令(如
- CPU时间片耗尽:
- 现代操作系统使用时间片轮转调度算法(time-slicing)来分配CPU时间。
- 在时间片内,空循环将会持续运行,直到时间片耗尽,操作系统再将CPU资源分配给其他进程。
- 在多核CPU系统中,如果每个核心上都有这样的空循环,每个核心的CPU使用率都将达到100%。
大概理解就是:CPU是分时调度的,进程通过时间片申请调度,但是空循环中不存在阻塞,能够一直申请到时间片,于是就会耗尽CPU的调度时间,造成CPU占用100%!
问题拓展
在通过sleep(1)
降低CPU的占用的时候,忽然想起很久之前看到的,Java中sleep(0)
有什么作用,当时还不算太明白,结合今天的总结,大概最终目的也是让出分时片,防止一个线程对CPU的高占用吧。
在 Java 中,有时候开发者会刻意使用 Thread.sleep(0)
,这种操作虽然看起来奇怪,但确实有一些实际应用场景和好处。以下是一些主要原因和相关解释:
1. 让出 CPU 时间片
Thread.sleep(0)
可以让当前线程主动放弃 CPU 时间片,从而使调度器有机会重新安排线程的执行。尽管这不会让线程真正休眠(因为时间为 0),但它确实会触发线程调度器的重新调度。
多线程环境:在多线程环境中,如果有多个线程在竞争 CPU 资源,使用
Thread.sleep(0)
可以让当前线程让出 CPU 时间片,使其他线程有机会执行,从而实现更公平的调度。防止忙等待:在一些情况下,程序可能会进入一个忙等待的循环。如果线程执行了
Thread.sleep(0)
,它可以防止当前线程长时间占用 CPU,从而提高系统的整体响应性。
2. 提高系统的响应性
在某些情况下,使用 Thread.sleep(0)
可以提高系统的响应性,尤其是在 GUI 应用程序中。
- 用户界面更新:在 GUI 应用程序中,如果某个线程占用了大量的 CPU 时间,可能会导致界面卡顿。通过使用
Thread.sleep(0)
,可以让出时间片,使用户界面的更新线程有机会执行,从而提高用户体验。
3. 协助垃圾回收
在 Java 中,垃圾回收器(GC)通常在系统空闲时运行。使用 Thread.sleep(0)
可以让当前线程暂时放弃 CPU,使垃圾回收器有机会运行,清理内存。
- 内存管理:在内存紧张的情况下,适当的
Thread.sleep(0)
调用可以为垃圾回收器提供机会,减少内存压力。
4. 修复线程调度器的潜在问题
在某些平台和 JVM 实现中,线程调度器可能存在一些调度问题,通过使用 Thread.sleep(0)
可以触发调度器重新调度,有时可以避免一些潜在的问题。
代码示例
1 | public class SleepZeroExample { |
在这个示例中,Thread.sleep(0)
在循环中被调用,提示调度器可以重新调度其他线程。尽管当前线程不会真正进入休眠,但它有机会让其他线程获得执行时间。
需要注意的事项
- 平台依赖性:
Thread.sleep(0)
的行为可能会因平台和 JVM 实现而异。在某些平台上,它可能没有预期的效果。 - 调度器实现:不同的操作系统和 JVM 可能有不同的线程调度实现,因此需要根据具体情况进行测试和优化。
- 使用场景:
Thread.sleep(0)
并不是解决所有线程调度问题的万能方法,应根据具体场景和需求谨慎使用。
总结
使用 Thread.sleep(0)
可以让当前线程主动放弃 CPU 时间片,触发线程调度器的重新调度,从而改善系统的响应性、防止忙等待、协助垃圾回收,并修复一些潜在的调度问题。然而,其效果可能因平台和 JVM 实现而异,因此需要根据具体情况进行测试和优化。