Python 子进程终止并超时

Max*_*Max 2 python subprocess

我正在使用 python 中的 subprocess 模块运行一些 shell 脚本。如果 shell 脚本运行时间过长,我喜欢终止子进程。我认为如果我将 传递timeout=30给我的run(..)陈述就足够了。

这是代码:

try:
    result=run(['utilities/shell_scripts/{0} {1} {2}'.format(
                        self.language_conf[key][1], self.proc_dir, config.main_file)],
                shell=True,
                check=True,
                stdout=PIPE,
                stderr=PIPE, 
                universal_newlines=True, 
                timeout=30,
                bufsize=100)
except TimeoutExpired as timeout:
Run Code Online (Sandbox Code Playgroud)

我已经用一些运行 120 秒的 shell 脚本测试了这个调用。我预计子进程会在 30 秒后被终止,但实际上该进程正在完成 120 秒的脚本,然后引发超时异常。现在的问题是如何通过超时终止子进程?

Jea*_*bre 7

文档明确指出应该终止该进程:

来自以下文档subprocess.run

“超时参数传递给 Popen.communicate()。如果超时到期,子进程将被杀死并等待。在子进程终止后,将重新引发 TimeoutExpired 异常。”

但是在您的情况下,您正在使用shell=True,并且我以前见过类似的问题,因为阻塞进程是 shell 进程的子进程。

shell=True如果您正确分解参数并且您的脚本具有适当的shebang,我认为您不需要。你可以试试这个:

result=run(
  [os.path.join('utilities/shell_scripts',self.language_conf[key][1]), self.proc_dir, config.main_file],  # don't compose argument line yourself
            shell=False,  # no shell wrapper
            check=True,
            stdout=PIPE,
            stderr=PIPE, 
            universal_newlines=True, 
            timeout=30,
            bufsize=100)
Run Code Online (Sandbox Code Playgroud)

请注意,我可以在 Windows 上很容易地重现这个问题(使用Popen,但它是一样的):

import subprocess,time

p=subprocess.Popen("notepad",shell=True)
time.sleep(1)
p.kill()
Run Code Online (Sandbox Code Playgroud)

=> 记事本保持打开状态,可能是因为它设法与父 shell 进程分离。

import subprocess,time

p=subprocess.Popen("notepad",shell=False)
time.sleep(1)
p.kill()
Run Code Online (Sandbox Code Playgroud)

=> 记事本在 1 秒后关闭

有趣的是,如果你删除time.sleep(),kill()甚至shell=True可能因为它成功杀死了正在启动的外壳notepad

我并不是说您有完全相同的问题,我只是证明shell=True出于多种原因这是邪恶的,而无法终止/超时进程是另一个原因。

但是,如果shell=True出于某种原因需要,您可以使用psutil来杀死所有的孩子。在这种情况下,最好使用Popen以便直接获取进程 ID:

import subprocess,time,psutil

parent=subprocess.Popen("notepad",shell=True)
for _ in range(30): # 30 seconds
    if parent.poll() is not None:  # process just ended
      break
    time.sleep(1)
else:
   # the for loop ended without break: timeout
   parent = psutil.Process(parent.pid)
   for child in parent.children(recursive=True):  # or parent.children() for recursive=False
       child.kill()
   parent.kill()
Run Code Online (Sandbox Code Playgroud)

(来源:如何从 python 中杀死进程和子进程?

该示例也会杀死记事本实例。