Python多进程守护程序中的僵尸进程问题

7

在研究Python守护进程后,我发现这篇文章是最强大的:http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/

现在我正在尝试在守护进程类中实现一个工作进程池,我相信它已经在工作了(我还没有彻底测试代码),除了关闭时出现一个僵尸进程。我已经阅读了需要等待子进程的返回代码,但我还不能确定我需要如何做。

以下是一些代码片段:

def stop(self):
    ...
    try:
        while 1:
            self.pool.close()
            self.pool.join()
            os.kill(pid, SIGTERM)
            time.sleep(0.1)
    ...

在这里,我尝试了 os.killpg 和一些 os.wait 函数,但没有改善。我还尝试了在 os.kill 前后使用 closing/joining 池。目前这个循环永远不会结束,只要它遇到了 os.kill,就会产生僵尸进程。 self.pool = Pool(processes=4) 出现在守护进程的 __init__ 部分。从 start(self) 后执行的 run(self) 中,我将调用 self.pool.apply_async(self.runCmd, [cmd, 10], callback=self.logOutput)。然而,在查看此问题之前,我想解决这个僵尸进程。
如何在守护进程内正确实现池以避免出现僵尸进程?

你的守护程序中是否有处理SIGCHLD的处理程序? - tMC
不,我没有这样的处理程序。我唯一拥有的处理程序用于 runCmd() 函数中使用的超时,即 signal.signal(signal.SIGALRM, self.handler)。这里处理程序引发一个自定义异常,说明命令已经超过了分配的执行时间。为什么我需要这个处理程序?我以为 multiprocessing 在 pool.closepool.join 中会处理它。坦白地说,我不知道进程从哪里来,因为我没有调用 apply_async,所以我没有工作进程或回调线程。 - Aaron Robinson
1个回答

5
在不知道子进程/守护进程中正在发生什么的情况下,无法百分之百地确定答案是否正确,但可以考虑这是否是原因。由于您的子进程中有工作线程,因此实际上需要构建一些逻辑来加入所有这些线程,以便在收到SIGTERM时退出进程(即使进程退出,您也可能无法优雅地退出)。为此,您需要:
  • 编写一个信号处理程序,在子进程/守护进程中使用该处理程序捕获SIGTERM信号并触发主线程的事件
  • 在子进程/守护进程的主线程中安装信号处理程序(非常重要)
  • SIGTERM的事件处理程序必须向子进程/守护进程中的所有线程发出停止指令
  • 所有线程完成后都必须执行join()操作(如果您假设SIGTERM将自动销毁所有内容,则还需实现此逻辑)
  • 一旦所有内容都已加入并清理完毕,就可以退出主线程

如果您有用于I/O和各种其他任务的线程,则这将是一项真正的繁琐工作。

此外,通过实验我发现,当您使用信号处理程序时,特定的事件监听器策略很重要。例如,如果您使用select.select(),则必须使用超时并在超时发生时重试;否则,您的信号处理程序将无法运行。如果您有一个Queue.Queue对象用于事件,并且您的事件监听器调用其.get()方法,则必须使用超时,否则您的信号处理程序将无法运行。(在VM中实现的“真正”信号处理程序会运行,但是除非您使用超时,否则Python信号处理程序不会运行。)

祝好运!


我稍微仔细看了一下,我不认为这是问题所在。然而,我意识到这篇文章在描述问题方面极其缺乏。如果你仍然对这个问题感兴趣,我已经重新提出了更详细的问题描述,请参见这里:https://dev59.com/vFjUa4cB1Zd3GeqPT7rT - Aaron Robinson

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