Python守护进程无法杀死其子进程

21

使用python-daemon时,我会像这样创建子进程:

import multiprocessing

class Worker(multiprocessing.Process):
   def __init__(self, queue):
      self.queue = queue # we wait for things from this in Worker.run()

   ...

q = multiprocessing.Queue()

with daemon.DaemonContext():
    for i in xrange(3):
       Worker(q)

    while True: # let the Workers do their thing
       q.put(_something_we_wait_for())

当我使用Ctrl-C或SIGTERM等方式杀死父守护进程时(即不是Worker),子进程不会死亡。如何杀死子进程?
我的第一个想法是使用atexit 来杀死所有的工作进程,像这样:
 with daemon.DaemonContext():
    workers = list()
    for i in xrange(3):
       workers.append(Worker(q))

    @atexit.register
    def kill_the_children():
        for w in workers:
            w.terminate()

    while True: # let the Workers do their thing
       q.put(_something_we_wait_for())

然而,恶魔的孩子是很棘手的事情,我希望能得到关于如何处理这个问题的想法和建议。

谢谢。


12
杀害自己的孩子似乎是一件“邪恶”的事情...... - ewall
肯定的。这个守护进程不符合规范。 - Pekka
16
这不是Python吗?你不能只是做from evil import infanticide或者类似的事情吗? - Syntactic
3个回答

32

你的选择有点有限。如果在Worker类的构造函数中执行self.daemon = True不能解决问题,尝试在父进程中捕获信号(例如SIGTERM, SIGINT)也不起作用,你可能需要尝试相反的解决方案 - 与其让父进程杀死子进程,不如在父进程死亡时让子进程自杀。

第一步是给Worker的构造函数传递父进程的PID(可以使用os.getpid()实现)。然后,在工作循环中不要只是执行self.queue.get(),而是像这样做:

waiting = True
while waiting:
    # see if Parent is at home
    if os.getppid() != self.parentPID:
        # woe is me! My Parent has died!
        sys.exit() # or whatever you want to do to quit the Worker process
    try:
        # I picked the timeout randomly; use what works
        data = self.queue.get(block=False, timeout=0.1)
        waiting = False
    except queue.Queue.Empty:
        continue # try again
# now do stuff with data

上面的解决方案检查父进程PID是否与原来不同(也就是说,如果子进程被initlauchd接管,因为父进程已经死亡)-请参见此处。但是,如果由于某些原因这种方法不起作用,您可以将其替换为以下函数(改编自这里):

def parentIsAlive(self):
    try:
        # try to call Parent
        os.kill(self.parentPID, 0)
    except OSError:
        # *beeep* oh no! The phone's disconnected!
        return False
    else:
        # *ring* Hi mom!
        return True

现在,当父进程死亡(不管是什么原因),子进程会像苍蝇一样自动死亡 - 就像你所希望的那样,你这个守护进程! :-D


2
我真的很想给你超过1分的评分,哈哈。 - edomaur
这不是忙等待吗?因为队列.get不再阻塞了?在我看来,最好在主线程中使用atexit插入一个特殊对象(如None)到队列中,让子进程在获取到该对象时使用sys.exit()。 - Cedric

4
当子进程首次创建时(假设在self.myppid中),您应该存储父进程的pid,当self.myppidgetppid()不同意味着父进程已经死亡。
为了避免一遍又一遍地检查父进程是否已更改,您可以使用PR_SET_PDEATHSIG,该方法在信号文档中有描述。

5.8 Linux的“父进程死亡”信号

对于每个进程,都有一个变量pdeath_signal,它在fork()或clone()后初始化为0。它给出了进程在其父进程死亡时应收到的信号。

在这种情况下,如果您想要进程终止,则只需将其设置为SIGHUP,如下所示:
prctl(PR_SET_PDEATHSIG, SIGHUP);

1
嗯,那其实可能行得通 - 如果父进程死亡(即init(对于Linux)或launchd(对于Mac OS X)接管了子进程),则os.getppid()应该返回1。 - Daniel G

2
Atexit不能解决问题,它只在成功的非信号终止时运行--请参阅文档顶部的注释。您需要通过其中一种方式设置信号处理。
听起来更容易的选项:根据http://docs.python.org/library/multiprocessing.html#process-and-exceptions上的说明,在您的工作进程上设置守护进程标志。
听起来有点困难的选择: PEP-3143似乎意味着在python-daemon中有一种内置的方法来挂钩程序清理需求。

谢谢Arthur。顺便说一下,我发布的一个相关问题是https://dev59.com/rXE85IYBdhLWcg3w64EA——它涵盖了其中一些主题。我想知道确保子进程被杀死(或不成为僵尸)的模式-我猜多进程守护进程标志将有所不同-它是否涵盖了所有情况?哪些情况? - Brian M. Hunt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接