前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >小说python中的孤儿进程

小说python中的孤儿进程

作者头像
用户2196567
发布2018-09-20 17:33:10
1.7K0
发布2018-09-20 17:33:10
举报
文章被收录于专栏:chafezhouchafezhou

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

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

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

代码语言:javascript
复制
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()将进程组中的所有进程(孙孙进程)杀死,然后自己退出。

代码语言:javascript
复制
def exit_when_parent_or_child_dies(sig):
    gid = os.getpgrp()
    signal(SIGCHLD, make_child_die_signal_handler(gid))

    try:
        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()
            time.sleep(PARENT_POLL_INTERVAL)

到此,整个流程就清晰了,通过设置孙进程和孙孙进程的进程组ID为子进程的进程ID,当主进程退出,子进程被init进程接管时,通过killpg将同一个进程组ID的孙进程和孙孙进程中止。

但如果仔细看代码,

exit_when_parent_or_child_dies方法中:

if os.getppid() == 1: 永远执行不到,因为父进程退出时,捕获如下信号

代码语言:javascript
复制
signal(SIGINT, handler)
signal(SIGQUIT, handler)
signal(SIGTERM, handler)
signal(SIGCHLD, make_child_die_signal_handler(gid, sig))

而这些信号处理方法中都会通过killpg杀死进程组,子进程也属于这个进程组,也会被kill掉。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-08-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 chafezhou 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档