当你使用ps -ef 查看系统进程时,如果发现了带有defunct字样的进程信息时,恭喜你,你成功的制造了僵尸进程。僵尸进程产生的原因是,子进程已经退出了,但是父进程还在运行,可是父进程并没有对子进程进行回收,这就导致子进程一直存在,对资源是一种浪费,如果僵尸进程过多,系统性能会下降。在python项目里,如果不能正确的使用subprocess模块的Popen类,就很容易产生僵尸进程。
父进程先退出,子进程还在运行,这时,子进程被init收养,当子进程结束后,init负责对子进程的资源进行回收,这种情况下,不会产生僵尸进程
父进程在启动子进程后,执行wait(),子进程退出后,这个wait()操作就负责回收子进程,这样也不会产生僵尸进程。但这样做有个致命的问题,wait是阻塞的,如果进行wait,主进程就什么都做不了。
这种情况,百分百会产生僵尸进程,直到父进程退出,僵尸进程由init收养,init负责执行wait()处理。
这样,通过两次fork,就避免了僵尸进程的产生
子进程在退出时,会向父进程发送SIGCHLD信号,SIG_IGN 是忽略的意思,在python代码里加入这样一段
import signal
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
这段代码通知内核,自己对子进程的退出不关心,让内核来回收,这个方法看起来十分简单,但是并不是在所有系统上都有效,在mac和linux系统下,这种方法是可行的,其他的系统我没有试验,强调一点,在docker环境下,init进程其实是docker主程序的进程,因此在docker里,即便是linux系统这种方法也不行。
为SIGCHLD信号注册一个处理方法,当子进程退出后,使用waitpid来处理子进程的退出,如此处理,父进程不会发生阻塞
test.py
import errno
import time
import sys
import os
import signal
from subprocess import Popen
def wait_child(signum, frame):
try:
while True:
childpid, status = os.waitpid(-1, os.WNOHANG)
if childpid == 0:
print('没有立即可用的子进程')
break
exitcode = status >> 8
print('子进程 %s 退出,状态码是 exitcode %s' % childpid, exitcode)
except OSError as e:
if e.errno == errno.ECHILD:
print("没有需要等待wait的子进程")
else:
raise
signal.signal(signal.SIGCHLD, wait_child)
Popen([sys.executable, './app.py'])
for i in range(10):
time.sleep(5)
app.py
import time
for i in range(3):
print(i)
time.sleep(5)
执行test.py 脚本后,会通过子进程执行app.py, app.py会先结束,结束时会发送signal.SIGCHLD给父进程test.py, 在test.py中对信号进行处理。这里对os.waitpid(-1, os.WNOHANG) 进行解释
QQ交流群: 211426309