【问题标题】:python kill parent process but child process leftpython杀死父进程但子进程离开
【发布时间】:2016-03-05 08:46:00
【问题描述】:

当我试图杀死一个 python 进程时,通过os.system 启动的子进程不会同时终止。

Killing child process when parent crashes in pythonPython Process won't call atexit (atexit 看起来不适用于信号)

这是否意味着我需要自己处理这种情况?如果是这样,首选的方法是什么?

> python main.py
> ps
4792 ttys002    0:00.03 python run.py
4793 ttys002    0:00.03 python loop.py 
> kill -15 4792
> ps 
4793 ttys002    0:00.03 python loop.py

示例代码:

main.py

import os
os.system('python loop.py')

循环.py

import time

while True:
    time.sleep(1000)

更新1

我做了一些实验,找到了一个可行的版本,但仍然对逻辑感到困惑。

import os
import sys
import signal
import subprocess


def sigterm_handler(_signo, _stack_frame):
    # it raises SystemExit(0):
    print 'go die'
    sys.exit(0)


signal.signal(signal.SIGTERM, sigterm_handler)

try:
    # os.system('python loop.py') 
    # use os.system won't work, it will even ignore the SIGTERM entirely for some reason 
    subprocess.call(['python', 'loop.py'])
except:
    os.killpg(0, signal.SIGKILL)

【问题讨论】:

  • 这里的问题可能是因为你用SIGKILL 信号(-9)杀死了它。这意味着它将在不调用任何其他信号处理程序的情况下终止进程,因为无法捕获SIGKILL。您应该改用SIGTERM,并在所有其他方法都失败时使用SIGKILL
  • @LoïcFaure-Lacroix 抱歉,这实际上是一个错字。我用 SIGTERM 终止进程。
  • 您可能想在此处阅读有关信号的内容:pymotw.com/2/subprocess
  • @lucemia:我的回答不准确。虽然它不会更改代码示例。见the update
  • @J.F.Sebastian 谢谢,你的回答真的很有用!

标签: python subprocess


【解决方案1】:

kill -15 4792 在您的示例中将SIGTERM 发送到run.py - 它不向loop.py(或其父shell)发送任何内容。 SIGTERM 默认不会传播到进程树中的其他进程。

os.system('python loop.py') 至少启动 两个 进程 shell 和 python 进程。你不需要它;使用subprocess.check_call(),在没有隐式外壳的情况下运行单个子进程。顺便说一句,如果你的 subprocess is a Python script; consider importing it and running corresponding functions instead.

os.killpg(0, SIGKILL) 向当前进程组发送SIGKILL 信号。 shell 为每个管道创建一个新的进程组(一个作业),因此父级中的os.killpg() 对子级没有影响(请参阅更新)。见How to terminate a python subprocess launched with shell=True

#!/usr/bin/env python
import subprocess
import sys

try:
    p = subprocess.Popen([executable, 'loop'])
except EnvironmentError as e: # 
    sys.exit('failed to start %r, reason: %s' % (executable, e))
else:
    try: # wait for the child process to finish
        p.wait()
    except KeyboardInterrupt: # on Ctrl+C (SIGINT)
        #NOTE: the shell sends SIGINT (on CtrL+C) to the executable itself if
        #  the child process is in the same foreground process group as its parent 
        sys.exit("interrupted")

更新

似乎os.system(cmd) 没有为cmd 创建新的进程组:

>>> import os
>>> os.getpgrp()
16180
>>> import sys
>>> cmd = sys.executable + ' -c "import os; print(os.getpgrp())"'
>>> os.system(cmd) #!!! same process group
16180
0
>>> import subprocess
>>> import shlex
>>> subprocess.check_call(shlex.split(cmd))
16180
0
>>> subprocess.check_call(cmd, shell=True)
16180
0
>>> subprocess.check_call(cmd, shell=True, preexec_fn=os.setpgrp) #!!! new
18644
0

因此,您的示例中的 os.system(cmd) 应该被 os.killpg() 调用杀死。

如果我在 bash 中运行它;它确实为每个管道创建了一个新的进程组:

$ python -c "import os; print(os.getpgrp())"
25225
$ python -c "import os; print(os.getpgrp())"
25248

【讨论】: