pytest本身不创建子进程,但测试或被测代码调用subprocess.popen、os.fork()或processpoolexecutor时,若未显式wait/shutdown,子进程退出后因父进程未回收而变成僵尸进程。

为什么pytest会留下僵尸进程
pytest本身不创建子进程,但你的测试代码或被测代码里调用了 subprocess.Popen、os.fork()、或第三方库(如 concurrent.futures.ProcessPoolExecutor)时,若没显式清理,子进程退出后变成僵尸进程。Linux下父进程没调用 wait() 或 waitpid(),子进程的退出状态就一直挂在进程表里。
测试中如何避免Popen产生僵尸
最常见场景是测试里用 subprocess.Popen 启动外部命令但忘了回收。关键不是“启动”,而是“收尾”:
- 永远用
with语句包装Popen(需 Python 3.7+),它会自动调用wait()或communicate() - 不用
with时,必须显式调用proc.wait()或proc.communicate()—— 即使你只关心启动成功与否,也要等它结束 - 禁止只调用
proc.terminate()或proc.kill()就不管了;终止后仍需wait()确保回收
示例正确写法:
import subprocess <p>def test_something(): with subprocess.Popen(["sleep", "1"]) as proc: proc.wait() # 或直接让 with 自动处理 </p>
多进程池(ProcessPoolExecutor)在测试中怎么安全关闭
pytest运行多个测试函数,如果某个测试里创建了 ProcessPoolExecutor 但没 shutdown,它的 worker 进程可能持续存活,后续测试再建新池时旧进程还在,容易叠加成僵尸。
- 务必在
teardown阶段(如yieldfixture 的finally块)调用executor.shutdown(wait=True) - 避免在 module-level 或 class-level 创建全局 executor;每个测试应独占实例
- 如果用
concurrent.futures.as_completed(),确保所有 future 都已 done,否则 shutdown 可能阻塞或跳过清理
如何验证僵尸进程是否真被清掉了
别只靠 ps 看不到就放心。Linux 下僵尸进程状态是 Z,且 PPID 是 1(被 init 收养),说明父进程已死但没 wait:
- 运行测试后执行:
ps aux | grep ' Z '(注意空格包围 Z) - 结合
pgrep -P $$查当前 pytest 进程的直系子进程,看是否有状态为Z的 - 更可靠方式:在测试前/后用
len(os.listdir('/proc'))统计进程数变化,或用psutil.pids()检查新增未回收进程
真正难处理的是那些 fork 后 exec 失败、或子进程自己又 fork 出孙进程却没等它们的场景——这时光等 direct child 不够,得确保整个进程树都 clean up。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











