相关文章推荐
温暖的可乐  ·  模式识别Pattern ...·  4 月前    · 
近视的野马  ·  Authorization | ...·  5 月前    · 
愤怒的佛珠  ·  Python基础 ...·  1 年前    · 
备案 控制台
学习
实践
活动
专区
工具
TVP
写文章
专栏首页 chafezhou 小说python中的孤儿进程
1 0

海报分享

小说python中的孤儿进程

僵尸进程 ,大家都会对其嗤之以鼻,敬而远之,毕竟臭名在外。

孤儿进程 ,大家对其都很宽容,甚至可以说是放纵,只因系统会收留。

然而,在实际应用中,孤儿进程虽然不会给系统造成直接性的危害,但更多时候会对业务造成一些影响,如当子进程为一个基于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()