分享文章到朋友圈
海报分享
僵尸进程 ,大家都会对其嗤之以鼻,敬而远之,毕竟臭名在外。
孤儿进程 ,大家对其都很宽容,甚至可以说是放纵,只因系统会收留。
然而,在实际应用中,孤儿进程虽然不会给系统造成直接性的危害,但更多时候会对业务造成一些影响,如当子进程为一个基于tcp的socket服务时,会造成主进程再次启动时无法启动,端口被占用。主进程退出了,子进程会因为无法获得某些资源,而变成 业务上的"僵尸进程" ,这实际也是资源浪费。对于一些有进程监控的服务来说,可能会造成业务主服务无法重启,或是进程不可控。
鉴于这些情况下,很多时候是不希望产生孤儿进程的,子进程应随父进程结束而结束。
本文就小说一把如何做一个有担当的"父亲",不要不负责任的"一走了之",随意丢弃自己的"孩子们"。
什么是孤儿进程
孤儿进程: 一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成中止后的资源回收工作
通过下面的具体例子,具体看看
centralized_in_out 服务会启动8个子进程,父进程ID为 5310 ,子进程ID为 5312-5319
将父进程(5310)kill掉,可以看到子进程5312-5319全由ID为 1 的进程接管
如何做
上面看到子进程5312-5319被init进程接管了,但这不是我想要的结果,当前业务中,会再次拉起 centralized_in_out 服务,会再启动8个子进程,这样进程数太多,会失控,不符合业务需求。
我需要的是”父子共进退“,如何做呢?
豆瓣的工程师们,已经给出了解决办法,具体参见:
https://github.com/douban/CaoE
修改代码,用起来,效果如下
为什么
豆瓣工程师给出了解决办法,不能只拿来用用,得问几个为什么?通过什么实现的?为什么要这么做呢?
下面具体分析下实现方法:
1. 方法概述
实现思路是通过创建一个子进程和孙子进程,子进程会监控父进程的状态,当检测到父进程退出后,会给进程组发送信号通知杀死孙子进程及其子进程。
这里涉及到 进程组 和 信号 两个重要概念,下面具体阐述。
2. 概念阐述
进程组: 每个进程都会属于一个进程组(process group),每个进程组中可以包含多个进程。进程组会有一个领导进程 (process group leader),领导进程的PID成为进程组的ID (process group ID, PGID),用来标识进程组。
如下图所示, centralized_in_out 服务父进程的ID为5538(它的PGID为5538),子进程ID为5540(它的PGID为5540),孙子进程的ID为5541(它的PGID为5540),孙孙进程5542-5549的PGID都为5541
信号: 具体概念这里不多说了,有些大,而且晦涩难懂。主要涉及信号定义和处理函数的注册绑定,后面结合代码具体说明
3. 实现详解
def install(fork=True, sig=SIGTERM): def _reg(gid): handler = make_quit_signal_handler(gid, sig) signal(SIGINT, handler) signal(SIGQUIT, handler) signal(SIGTERM, handler) signal(SIGCHLD, make_child_die_signal_handler(gid, sig)) if not fork: _reg(os.getpid()) return pid = os.fork() if pid == 0: # child process os.setpgrp() pid = os.fork() if pid != 0: # still in child process exit_when_parent_or_child_dies(sig) # grand child process continues... else: # parent process gid = pid _reg(gid) while True: pause()
通过两次fork,创建子进程(ID:5540)和孙进程(ID:5541),
其中子进程中有重要的一步, os.setpgrp() 将子进程的进程组ID(5540)设为当前进程组的ID,后面孙进程和孙孙进程的进程组ID都为5540。
子进程在 exit_when_parent_or_child_dies 方法中循环等待父进程状态,当PPID为1时,说明父进程已退出,通过 killpg() 将进程组中的所有进程(孙孙进程)杀死,然后自己退出。
def exit_when_parent_or_child_dies(sig): gid = os.getpgrp() signal(SIGCHLD, make_child_die_signal_handler(gid)) import prctl signal(SIGHUP, make_quit_signal_handler(gid)) # give me SIGHUP if my parent dies prctl.set_pdeathsig(SIGHUP) while True: pause() except ImportError: # fallback to polling status of parent while True: if os.getppid() == 1: # parent died, suicide signal(SIGTERM, SIG_DFL) os.killpg(gid, sig) sys.exit()