【问题标题】:Python avoid orphan processesPython避免孤儿进程
【发布时间】:2014-12-06 12:18:12
【问题描述】:

我正在使用 python 对某些东西进行基准测试。这可能需要很长时间,我想设置一个(全局)超时。我使用以下脚本(总结):

class TimeoutException(Exception):
    pass
def timeout_handler(signum, frame):
    raise TimeoutException()

# Halt problem after half an hour
signal.alarm(1800)
try:
    while solution is None:
        guess = guess()
        try:
            with open(solutionfname, 'wb') as solutionf:
                solverprocess = subprocess.Popen(["solver", problemfname], stdout=solutionf)
                solverprocess.wait()
        finally:
            # `solverprocess.poll() == None` instead of try didn't work either
            try:
                solverprocess.kill()
            except:
                # Solver process was already dead
                pass
except TimeoutException:
    pass
# Cancel alarm if it's still active
signal.alarm(0)

但是它有时会不断产生孤立进程,但我无法可靠地重新创建这种情况。有谁知道防止这种情况的正确方法是什么?

【问题讨论】:

标签: python subprocess


【解决方案1】:

你只需要在杀死进程后wait

【讨论】:

  • 所以如果p.kill() 被调用但p 在python 退出之前还没有退出p 根本就没有被杀死?我会测试这是不是真的,但为什么会这样?据我了解p.kill()SIGKILL 发送到p,即使python 退出,这也会导致p 死亡。
  • @dtech 进程 is 被杀死,但 内核 确实 not 删除它,因为它正在等待父进程读取其状态。
  • @dtech:进程已死(SIGKILL always works),但僵尸仍然存在,直到它被收割。如果原始父进程已死,则专用进程(如init 1)将收集状态。注意:an orphan process must be alive by definition(它的父母已经死了)。您的代码会创建僵尸,而不是孤儿。
【解决方案2】:

kill() 方法的文档指出:

杀死孩子。在 Posix 操作系统上,该函数将SIGKILL 发送给孩子。 在 Windows 上,kill()terminate() 的别名。

换句话说,如果您不在 Windows 上,那么您只是向子进程发送信号。 这将创建一个僵尸进程,因为父进程没有读取子进程的返回值。

kill()terminate() 方法只是 send_signal(SIGKILL)send_signal(SIGTERM) 的快捷方式。

尝试在kill() 之后添加对wait() 的调用。这甚至显示在 communicate() 文档下的示例中:

proc = subprocess.Popen(...)
try:
    outs, errs = proc.communicate(timeout=15)
except TimeoutExpired:
    proc.kill()
    outs, errs = proc.communicate()

注意在kill() 之后对communicate() 的调用。 (相当于调用wait(),同时擦除子进程的输出)。


我想澄清一件事:您似乎并不完全了解 僵尸进程 是什么。僵尸进程是一个终止进程。内核将进程保存在进程表中,直到父进程读取其退出状态。我相信子进程使用的所有内存实际上都被重用了;内核只需要跟踪此类进程的退出状态。

所以,您看到的僵尸进程没有运行。他们已经完全死了,这就是他们被称为僵尸的原因。它们在进程表中“活着”,但根本没有真正运行。

调用wait() 正是这样做的:等到子进程结束并读取退出状态。这允许内核从进程表中删除子进程。

【讨论】:

  • 你说得对,我的意思是“孤儿进程”。它们绝对没有死,因为它们一直在消耗 CPU 和 RAM。我会试试你和丹尼尔的建议。
  • @dtech 这是一个完全不同的场景。孤儿进程在其父进程死亡时发生。然后它成为init 的孩子。在您的示例中,我真的不知道这些可能来自哪里。需要有关您正在启动的子流程的更多信息。另外,您确定要kill 那个子进程吗? 可能会导致创建孤立进程,因为killing 不允许子进程执行任何清理。您应该首先尝试致电terminate,然后如果失败,请致电kill
  • 你说得对,SIGTERM 可能更好,但我不知道 SIGKILL 如何导致进程不被杀死并成为孤立进程。子进程是SAT-solver。我会在一段时间后尝试终止并调用 kill。
  • @dtech sat 求解器可能会产生一些子进程来执行实际计算。您的SIGKILL杀死主进程。使用SIGTERM可能让主进程在退出之前终止其子进程,这取决于它的实施情况。
【解决方案3】:

在linux上,你可以使用python-prctl

定义一个 preexec 函数,例如:

def pre_exec():
    import signal
    prctl.set_pdeathsig(signal.SIGTERM)

让你的 Popen 调用通过它。

subprocess.Popen(..., preexec_fn=pre_exec)

就这么简单。现在,如果父进程死亡,子进程将死亡而不是成为孤儿。

如果你不喜欢 python-prctl 的外部依赖,你也可以使用旧的prctl。而不是

prctl.set_pdeathsig(signal.SIGTERM)

你会的

prctl.prctl(prctl.PDEATHSIG, signal.SIGTERM)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-01-08
    • 2011-10-12
    • 2012-10-17
    • 2020-09-09
    • 1970-01-01
    • 1970-01-01
    • 2018-07-05
    • 1970-01-01
    相关资源
    最近更新 更多