python僵尸进程产生的原因
在unix或unix-like的系统中,当一个子进程退出后,它就会变成一个僵尸进程,如果父进程没有通过wait系统调用来读取这个子进程的退出状态的话,这个子进程就会一直维持僵尸进程状态。
Zombieprocess-Wikipedia中是这样描述的:
OnUnixandUnix-likecomputeroperatingsystems,azombieprocessordefunctprocessisaprocessthathascompletedexecution(viatheexitsystemcall)butstillhasanentryintheprocesstable:itisaprocessinthe"Terminatedstate".Thisoccursforchildprocesses,wheretheentryisstillneededtoallowtheparentprocesstoreaditschild'sexitstatus:oncetheexitstatusisreadviathewaitsystemcall,thezombie'sentryisremovedfromtheprocesstableanditissaidtobe"reaped".Achildprocessalwaysfirstbecomesazombiebeforebeingremovedfromtheresourcetable.Inmostcases,undernormalsystemoperationzombiesareimmediatelywaitedonbytheirparentandthenreapedbythesystem–processesthatstayzombiesforalongtimearegenerallyanerrorandcausearesourceleak.
并且僵尸进程无法通过kill命令来清除。
本文将探讨如何手动制造一个僵尸进程以及清除僵尸进程的办法。
手动制造一个僵尸进程
为了便于后面讲解清除僵尸进程的方法,我们使用日常开发中经常使用的multiprocessing模块来制造僵尸进程(准确的来说是制造一个长时间维持僵尸进程状态的子进程):
$cattest_a.py frommultiprocessingimportProcess,current_process importlogging importos importtime logging.basicConfig( level=logging.DEBUG, format='%(asctime)-15s-%(levelname)s-%(message)s' ) defrun(): logging.info('exitchildprocess%s',current_process().pid) os._exit(3) p=Process(target=run) p.start() time.sleep(100)
测试:
$pythontest_a.py& [1]10091 $2017-07-2021:28:14,792-INFO-exitchildprocess10106 $psaux|grep10106 mozillazg101260.00.02434836740s006R+0:00.00grep10106 mozillazg101060.00.000s006Z0:00.00(Python)
可以看到,子进程10091变成了僵尸进程。
既然已经可以控制僵尸进程的产生了,那我们就可以进入下一步如何清除僵尸进程了。
清除僵尸进程有两种方法:
•第一种方法就是结束父进程。当父进程退出的时候僵尸进程随后也会被清除。
•第二种方法就是通过wait调用来读取子进程退出状态。我们可以通过处理SIGCHLD信号,在处理程序中调用wait系统调用来清除僵尸进程。
处理SIGCHLD信号
子进程退出时系统会向父进程发送SIGCHLD信号,父进程可以通过注册SIGCHLD信号处理程序,在信号处理程序中调用wait
系统调用来清理僵尸进程。$cattest_b.py
importerrno frommultiprocessingimportProcess,current_process importlogging importos importsignal importtime logging.basicConfig( level=logging.DEBUG, format='%(asctime)-15s-%(levelname)s-%(message)s' ) defrun(): exitcode=3 logging.info('exitchildprocess%swithexitcode%s', current_process().pid,exitcode) os._exit(exitcode) defwait_child(signum,frame): logging.info('receiveSIGCHLD') try: whileTrue: #-1表示任意子进程 #os.WNOHANG表示如果没有可用的需要wait退出状态的子进程,立即返回不阻塞 cpid,status=os.waitpid(-1,os.WNOHANG) ifcpid==0: logging.info('nochildprocesswasimmediatelyavailable') break exitcode=status>>8 logging.info('childprocess%sexitwithexitcode%s',cpid,exitcode) exceptOSErrorase: ife.errno==errno.ECHILD: logging.error('currentprocesshasnoexistingunwaited-forchildprocesses.') else: raise logging.info('handleSIGCHLDend') signal.signal(signal.SIGCHLD,wait_child) p=Process(target=run) p.start() whileTrue: time.sleep(100)
效果:
$pythontest_b.py& [1]10159 $2017-07-2021:28:56,085-INFO-exitchildprocess10174withexitcode3 2017-07-2021:28:56,088-INFO-receiveSIGCHLD 2017-07-2021:28:56,089-INFO-childprocess10174exitwithexitcode3 2017-07-2021:28:56,090-ERROR-currentprocesshasnoexistingunwaited-forchildprocesses. 2017-07-2021:28:56,090-INFO-handleSIGCHLDend $psaux|grep10174 mozillazg101940.00.02432788556s006R+0:00.00grep10174
可以看到,子进程退出变成僵尸进程后,系统给父进程发送了SIGCHLD信号,我们在SIGCHLD信号的处理程序中通过os.waitpid调用wait系统调用后阻止了子进程一直处于僵尸进程状态,从而实现了清除僵尸进程的效果。