【问题标题】:python - terminate child process when script invoked from bashpython - 从bash调用脚本时终止子进程
【发布时间】:2014-02-28 00:02:04
【问题描述】:

我有一个 python 脚本:zombie.py

from multiprocessing import Process
from time import sleep
import atexit

def foo():
    while True:
        sleep(10)

@atexit.register
def stop_foo():
    p.terminate()
    p.join()

if __name__ == '__main__':
    p = Process(target=foo)
    p.start()

    while True:
        sleep(10)

当我使用python zombie.py & 运行它并使用kill -2 终止父进程时,stop() 被正确调用并且两个进程都终止了。

现在,假设我有一个 bash 脚本zombie.sh:

#!/bin/sh

python zombie.py &

echo "done"

我从命令行运行./zombie.sh

现在,当父级被杀死时,stop() 永远不会被调用。如果我在父进程上运行kill -2,什么也不会发生。 kill -15kill -9 都只是杀死父进程,而不是子进程:

[foo@bar ~]$ ./zombie.sh 
done
[foo@bar ~]$ ps -ef | grep zombie | grep -v grep
foo 27220     1  0 17:57 pts/3    00:00:00 python zombie.py
foo 27221 27220  0 17:57 pts/3    00:00:00 python zombie.py
[foo@bar ~]$ kill -2 27220
[foo@bar ~]$ ps -ef | grep zombie | grep -v grep
foo 27220     1  0 17:57 pts/3    00:00:00 python zombie.py
foo 27221 27220  0 17:57 pts/3    00:00:00 python zombie.py
[foo@bar ~]$ kill 27220
[foo@bar ~]$ ps -ef | grep zombie | grep -v grep
foo 27221     1  0 17:57 pts/3    00:00:00 python zombie.py

这里发生了什么?如何确保子进程与父进程一起死亡?

【问题讨论】:

标签: python linux bash process multiprocessing


【解决方案1】:

atexitp.daemon = True 都不会真正确保子进程将与父亲一起死亡。接收 SIGTERM 不会触发 atexit 例程。

为了确保孩子在父亲去世时被杀死,您必须在父亲中安装一个信号处理程序。这样,您可以对大多数信号(SIGQUIT、SIGINT、SIGHUP、SIGTERM,...)做出反应,但不能对 SIGKILL;根本没有办法从接收它的进程中对该信号作出反应。

为所有有用的信号安装一个信号处理程序,并在该处理程序中杀死子进程。

【讨论】:

  • 安装信号处理程序也不会“真正确保”它。在 Linux 上,有 prctl(),但这有点过头了。
  • 父亲是什么意思? bash 脚本?我已经在python脚本中安装了atexit.register,它应该从kill -2响应SIGINT
  • 不是,我说的父进程就是你启动的Python脚本。通过使用subprocess 模块,它创建了一个子进程。
  • 实际上,不,atexit 对 Python 脚本的受控退出作出反应。 SIGINT 被解释器捕获并转换为KeyboardInterrupt 异常,然后有序传播并导致脚本的受控终止;因此atexit 被触发。如果任何其他信号(Python 解释器没有优雅地处理这个)终止进程,这将不再起作用。
【解决方案2】:

更新:此解决方案不适用于被信号终止的进程。


您的子进程不是zombie。它是活的。

如果您希望子进程在其父进程正常退出时被终止,则在p.start() 之前设置p.daemon = True。来自the docs

当一个进程退出时,它会尝试终止其所有守护进程。

查看the source code,很明显multiprocessing 使用atexit 回调来杀死其守护进程子进程,即如果父进程被信号杀死,它将无法工作。例如:

#!/usr/bin/env python
import logging
import os
import signal
import sys
from multiprocessing import Process, log_to_stderr
from threading import Timer
from time import sleep

def foo():
    while True:
        sleep(1)

if __name__ == '__main__':
    log_to_stderr().setLevel(logging.DEBUG)
    p = Process(target=foo)
    p.daemon = True
    p.start()

    # either kill itself or exit normally in 5 seconds
    if '--kill' in sys.argv:
        Timer(5, os.kill, [os.getpid(), signal.SIGTERM]).start()
    else: # exit normally
        sleep(5)

输出

$ python kill-orphan.py [INFO/Process-1] 子进程调用 self.run() [INFO/MainProcess] 进程关闭 [DEBUG/MainProcess] 运行优先级 >= 0 的所有“atexit”终结器 [INFO/MainProcess] 为守护进程 Process-1 调用 terminate() [INFO/MainProcess] 为进程 Process-1 调用 join() [DEBUG/MainProcess] 运行剩余的“atexit”终结器

注意“为守护进程调用 terminate()” 行。

输出(带有--kill

$ python kill-orphan.py --kill [INFO/Process-1] 子进程调用 self.run()

日志显示,如果父母被信号杀死,则不会调用“atexit”回调(ps 表明在这种情况下孩子还活着)。另见Multiprocess Daemon Not Terminating on Parent Exit

【讨论】:

  • 这在我的系统上不起作用。我正在运行Scientific Linux release 6.5 (Carbon)
  • @user545424:它在我的系统上也失败了(Ubuntu)。我希望它适用于SIGTERM 等信号。它适用于SIGINTkill -2)。
猜你喜欢
  • 2011-12-10
  • 2010-10-06
  • 1970-01-01
  • 2014-09-27
  • 2013-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-23
相关资源
最近更新 更多