朕喜技术(ZhenxiJS)
2026.08.29
1 分钟阅读

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%

根因链路:

  1. process_data 函数忘记将处理结果收集到 result 列表中 → 返回空列表
  2. 外层 while True 依赖 result 非空才退出循环 → 永远无法退出
  3. 循环体没有 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 分钟触发告警)

版权声明

本文为博主原创文章,遵循 CC BY-NC-SA 4.0 许可协议。 转载请注明出处:http://localhost/blogs/2026-08-29-1612-cpu-100-deadloop-python

分享这篇文章