$ ls ~yifei/notes/

父进程退出后如何退出子进程

Posted on:

Last modified:

当子进程退出的时候,父进程会收到 SIGCHLD 信号,从而可以采取相应的操作。但是当父进程退出的 时候,系统会把子进程的父进程更改为 pid=0init 进程,却不给子进程发送任何信号。

import os
import time

def loop_print():
    import time
    while True:
        print('child alive, %s' % time.time())
        time.sleep(1)

try:
    pid = os.fork()
except OSError:
    pass

if pid != 0:  # parent
    print("parent sleep for 2")
    time.sleep(2)
    print('parent quit')
else:
    loop_print()

当父进程退出的时候,子进程一直在不断地 print, 而没有退出。

如果想在父进程退出的时候,让子进程也退出。在 Python 中可以有如下几种做法。

设置子进程为 daemon

这里的 daemon 和系统的守护进程没有任何关系,是 quit_when_parent_dies 的意思。也就是当父进程 退出的时候,会自动尝试关闭 daemon=True 的子进程。

p = multiprocessing.Process(target=foo, daemon=True)
p.start()

昨天我已经吐槽过标准库的 multiprocessing 有很多坑,不出所望,在这个问题上 multiprocessing 依然提供了半个解法,只解决了一半问题......

正常情况下,当一个程序收到 SIGTERM 或者 SIGHUP 等信号的时候,multiprocessing 会调用每个 子进程的 terminate 方法,这样会给每个子进程发送 SIGTERM 信号,子进程就可以优雅退出。然而, 当异常发生的时候,父进程挂了,比如说收到了 SIGKILL 信号,那么子进程就得不到收割,也就变成了 孤儿进程。

所以说,multiprocessing 库只解决了半个问题,真遇到问题的时候就会坑你一把。

在子进程中设置 PDEATHSIG

在 Linux 中,进程可以要求内核在父进程退出的时候给自己发信号,使用系统调用 prctl。

prctl(PR_SET_PDEATHSIG, SIGHUP);

在 Python 中也有对应的包 python-prctl,可以在 子进程中这样使用,这样在父进程挂掉的时候,子进程就会收到 SIGHUP 信号:

# apt-get install build-essential libcap-dev
# pip install python-prctl

import signal
import prctl

prctl.set_pdeathsig(signal.SIGHUP)

以上面的程序为例:

import os
import time

def loop_print():
    import time
    import signal
    prctl.set_pdeathsig(signal.SIGTERM)
    while True:
        print("child alive, %s" % time.time())
        time.sleep(1)

try:
    pid = os.fork()
except OSError:
    pass

if pid != 0:  # parent
    print("parent sleep for 2")
    time.sleep(2)
    print("parent quit")
else:
    loop_print()

这次我们看到,在父进程退出的同时,子进程也退出了。

parent sleep for 2
child alive, 1539676057.5094635
child alive, 1539676058.5105338
parent quit

缺点:只支持 linux

父进程在终止的时候回收子进程

可以使用 atexit.register 在主进程中注册代码:

# pip install psutil

import psutil
import atexit
import os
import signal

@atexit.register
def kill_children():
    print("quitting, press Ctrl-C to force quit")
    current_process = psutil.Process()
    children = current_process.children(recursive=True)
    for child in children:
        print("Child pid is {}".format(child.pid))
        os.kill(child.pid, signal.SIGTERM)

使用 atexit收到 SIGTERM 的时候并不能触发,所以最好使用 signal 注册到主进程对应的信号上。

缺点是当使用 kill -9 或者使用 os._exit 的时候不会调用这些函数。

参考

  1. 官方文档

© 2016-2022 Yifei Kong. Powered by ynotes

All contents are under the CC-BY-NC-SA license, if not otherwise specified.

Opinions expressed here are solely my own and do not express the views or opinions of my employer.