一、什么是僵尸进程?
在 Linux 中,子进程调用 exit() 后,内核会保留其 PID、退出码等信息,直到父进程通过 wait() 或 waitpid() 取走。
如果父进程迟迟不取,子进程就处于 Z (zombie/defunct) 状态,俗称“僵尸进程”。
僵尸不耗内存,但占用进程表项;大量堆积会导致 fork: Resource temporarily unavailable。
二、僵尸进程的产生链路
- 父进程
fork()/multiprocessing.Process.start()/subprocess.Popen()创建子进程。 - 子进程 正常结束 或 被 OOM-killer 杀死(SIGKILL 9)。
- 父进程 未调用
wait/waitpid或 未注册SIGCHLD处理器。 - 子进程变成僵尸,PID 仍挂在进程表。
三、Python 中 3 种“零僵尸”写法
| 方案 | 代码示例 | 适用场景 |
|---|---|---|
| 1. 手动 join | p = Process(...); p.start(); p.join() | 少量子进程 |
| 2. with ProcessPoolExecutor | with ProcessPoolExecutor() as pool: pool.map(...) | 并行任务 |
| 3. SIG_IGN | signal.signal(signal.SIGCHLD, signal.SIG_IGN) | Unix 常驻服务 |
四、实战:OOM 场景对比
4.1 不收割 → 僵尸堆积
# oom_zombie.py
import multiprocessing as mp
def leak():
_ = [bytearray(1024*1024) for _ in range(10000)]
if __name__ == "__main__":
mp.Process(target=leak).start() # 不 join
input("press enter to quit...")
运行:
systemd-run --scope -p MemoryMax=100M python oom_zombie.py
再开终端:
watch -n1 'ps -eo pid,ppid,state,comm | grep python'
会看到 <defunct> 僵尸。
9530 9529 Z python3 <defunct>
4.2 with 上下文 → 零僵尸
# oom_with_ctx.py
from concurrent.futures import ProcessPoolExecutor
import os
def leak():
_ = [bytearray(1024*1024) for _ in range(10000)]
if __name__ == "__main__":
print("Parent PID:", os.getpid())
with ProcessPoolExecutor(max_workers=2) as pool:
pool.submit(leak)
pool.submit(leak)
print("All reaped, no zombies.")
五、小结
✅ 永远用 with:ProcessPoolExecutor、Pool、Popen
✅ 或显式 join() / wait()
✅ Unix 常驻服务可兜底 SIGCHLD = SIG_IGN
✅ 监控:ps -eo pid,ppid,state,comm | grep 'Z'
✅ 减少 OOM:控制并发、量化模型、加 swap
5686

被折叠的 条评论
为什么被折叠?



