CPU 使用率 100% 定位与修复:Python while True 死循环实战排查
问题背景
某业务服务器突然变慢,用户反馈 API 响应从原来的 50ms 变成 5 秒以上。执行 top 发现 CPU 使用率几乎 100%(98.2% us),用户态占比极高、内核态很低,说明有进程在疯狂消耗 CPU。进一步排查发现一个 Python 进程占用了 95%+ 的 CPU,陷入死循环。
关键词: CPU 100% 定位 / 死循环排查 / Python 死循环 / while True 无限循环 / top 分析 CPU / strace 定位死循环 / py-spy 查看 Python 堆栈
复现步骤
# 1. 启动问题环境(Docker 容器,cpus: 1 模拟单核)
cd scenario/2026-08-29-1600
docker compose up -d --build
# 2. 进入容器
docker compose exec app bash
# 3. 查看 CPU 占用最高的进程
ps aux --sort=-%cpu | head -5
top -bn1 | head -10
预期输出:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
3913 root 20 0 13580 7588 5128 R 100.0 0.0 0:08.31 python3
根本原因
问题出在 Python 脚本 task.py 中:
def process_data(items):
result = []
for item in items:
process_item(item)
# BUG: 漏了 result.append(...)!
return result # 永远返回空列表
while True:
result = process_data(items)
if result: # result 永远为空 → 永远为 False
break # 永远不会执行
# 没有 sleep → CPU 100%
根因链路:
process_data函数忘记将处理结果收集到result列表中 → 返回空列表- 外层
while True依赖result非空才退出循环 → 永远无法退出 - 循环体没有
sleep→ 紧密循环 → 单核 CPU 100%
解决办法
1. 定位高 CPU 进程
top -bn1 | head -10 # 看到 python3 占 100% CPU
ps aux --sort=-%cpu | head -5 # 确认进程是 python3 /app/task.py
2. 分析进程状态
cat /proc/3913/status | grep -E 'State|nonvoluntary'
State: R (running)— 进程一直处于运行状态nonvoluntary_ctxt_switches: 733— 非自愿上下文切换高(时间片被耗尽),是 CPU 密集死循环典型特征
3. 查看 Python 堆栈
py-spy dump --pid 3913
输出:
Thread 3913 (active+gil)
process_item (task.py:25)
process_data (task.py:36)
main (task.py:50)
<module> (task.py:61)
直接定位到死循环代码位置。
4. 修复代码
def process_item(item):
processed = item * 2
return processed # FIX: 返回处理结果
def process_data(items):
result = []
for item in items:
processed = process_item(item)
result.append(processed) # FIX: 收集结果
return result
# main 中直接调用,不再 while True 死循环
result = process_data(items)
验证
time python3 /app/fix_task.py
修复前: 进程永不退出,CPU 100% 修复后: 0.010 秒完成,CPU 降至 0%
[task] 开始处理数据...
[task] 处理完成,共 1000 条
[task] 任务正常结束!
real 0m0.010s
经验与避坑
1. CPU 100% 排查步骤速查
| 步骤 | 命令 | 目的 |
|---|---|---|
| 查看 CPU 占用 | top -bn1 | 找最高 CPU 进程 |
| 确认进程信息 | ps aux --sort=-%cpu | 确认 PID、进程名、命令行 |
| 查看进程状态 | cat /proc/<PID>/status | 看 State、上下文切换 |
| 追踪系统调用 | strace -p <PID> -f -c | 看进程在做什么(IO/网络/CPU 计算) |
| Python 堆栈 | py-spy dump --pid <PID> | 直接看 Python 代码执行位置 |
| 终止进程 | kill <PID> | 紧急止血 |
2. strace 无输出不代表进程空闲
坑: 如果 strace 附加成功但没有捕获到任何系统调用,不代表进程没有在干活——纯 CPU 计算(死循环)不产生系统调用。此时应该用 py-spy 或 gdb 看线程堆栈。
3. Docker 容器默认禁止 ptrace
坑: strace / py-spy / gdb 默认无法附加到 Docker 容器内的进程,报错 Operation not permitted。需要在 docker-compose 中加:
cap_add:
- SYS_PTRACE
security_opt:
- apparmor:unconfined
4. 非自愿上下文切换是死循环的信号
nonvoluntary_ctxt_switches 远远大于 voluntary_ctxt_switches,说明进程被内核强占时间片(因为不主动让出 CPU),是 CPU 密集死循环的强信号。
5. Python 死循环的常见原因
- 漏掉
break或return - 循环终止条件永远为 False(如
result永远为空) - 循环体没有
sleep或 IO 操作 - 变量在循环内被意外重置
- 使用了
while True但忘记加退出条件
6. 写代码的预防建议
- 避免裸
while True,必须写清晰的退出条件 - 处理函数务必
return结果,便于调用方判断完成状态 - 定期 review 代码中是否有死循环风险
- 设置 CPU 告警(如单进程 CPU > 90% 持续 5 分钟触发告警)
