超时时如何终止子进程?

21

我想尽可能快地重复执行一个子进程。但是有时这个进程会运行太久,因此我想杀死它。 我像下面这样使用signal.signal(...):

ppid=pipeexe.pid
signal.signal(signal.SIGALRM, stop_handler)

signal.alarm(1)
.....
def stop_handler(signal, frame):
    print 'Stop test'+testdir+'for time out'
    if(pipeexe.poll()==None and hasattr(signal, "SIGKILL")):
         os.kill(ppid, signal.SIGKILL)
         return False

但有时这段代码会试图阻止下一轮执行。 因为超时而停止测试/home/lu/workspace/152/treefit/test2 /bin/sh: /home/lu/workspace/153/squib_driver: 未找到 --- 这是下一次执行;该程序错误地停止了它。

有人知道如何解决吗?我想在规定时间内停止,而不是每秒执行1秒的time.sleep(n)。我不想这样做,我希望它可以少于1秒执行


那么基本上,如果子进程运行超过1秒钟,您想要终止它并启动下一个进程?这是正确的吗? - Chris Laplante
你如何创建子进程?因为看起来表达式__ppid=pipeexe.pid__获取的是即将运行的下一个子进程!!! - mouad
所以基本上,如果子进程运行超过1秒钟,您想要终止它并启动下一个进程?这是正确的吗?是的,没错。 - user504909
你如何创建子进程?pipeexe=subprocess.Popen(...) - user504909
因为看起来表达式ppid=pipeexe.pid获取了即将运行的下一个子进程!!我认为你可能是对的。但是如何确保pid在下一次运行时不会改变呢? - user504909
实际上,代码一开始运行得很好,但在多次终止进程后出现了问题。它开始出错了。 - user504909
6个回答

47

你可以像这样做:

import subprocess as sub
import threading

class RunCmd(threading.Thread):
    def __init__(self, cmd, timeout):
        threading.Thread.__init__(self)
        self.cmd = cmd
        self.timeout = timeout

    def run(self):
        self.p = sub.Popen(self.cmd)
        self.p.wait()

    def Run(self):
        self.start()
        self.join(self.timeout)

        if self.is_alive():
            self.p.terminate()      #use self.p.kill() if process needs a kill -9
            self.join()

RunCmd(["./someProg", "arg1"], 60).Run()

这个想法是创建一个线程来运行命令,如果超时时间超过某个合适的值,例如60秒,则终止它。


1
+1. 这种方法是我在构建系统中用于测试可执行文件的方法。 - Macke
6
同意加一。这似乎是我目前看到的最清晰的方法之一。稍作修改后,在 Linux 上的 Python < 2.6 上也可以使用 os.kill(self.p.pid, signal.SIGKILL) (或 SIGTERM 后跟 SIGKILL)。还可以用 self.out,self.err = self.p.communicate() 替换 self.p.wait(),以避免在子进程填满 stdout/stderr 管道时阻塞子进程。 - FredL
我将这段代码粘贴进去后,命令似乎已经开始运行了 - 但是,我需要一次只运行一个线程。启动一个进程,让它自然地完成或者在运行时间过长时终止它,然后再重新启动。 - Brian L Cartwright
能否从这里获取Popen的返回值? - galois
@galois:在Run()方法执行了self.start()语句之后,RunCmd实例的self.p包含了创建的subprocess.Popen实例,因此您可以通过这种方式访问它。您为什么想要这样做? - martineau

2

这是我编写的一个用于监控子进程执行的守护程序,我现在经常使用它,但我经验不够,可能存在一些缺陷:

import subprocess
import time

def subprocess_execute(command, time_out=60):
    """executing the command with a watchdog"""

    # launching the command
    c = subprocess.Popen(command)

    # now waiting for the command to complete
    t = 0
    while t < time_out and c.poll() is None:
        time.sleep(1)  # (comment 1)
        t += 1

    # there are two possibilities for the while to have stopped:
    if c.poll() is None:
        # in the case the process did not complete, we kill it
        c.terminate()
        # and fill the return code with some error value
        returncode = -1  # (comment 2)

    else:                 
        # in the case the process completed normally
        returncode = c.poll()

    return returncode   

使用方法:

 return = subprocess_execute(['java', '-jar', 'some.jar'])

评论:

  1. 在这里,看门狗超时时间是以秒为单位的;但是通过更改time.sleep()的值可以轻松更改为所需的任何值。必须相应地记录time_out
  2. 根据需要,此处可能更适合引发一些异常。

文档:我在subprocess模块的文档中遇到了一些困难,以理解subprocess.Popen不是阻塞的;进程是并行执行的(也许我在这里使用了不正确的词,但我认为它是可以理解的)。

但是,由于我编写的内容是线性执行的,因此我确实必须等待命令完成,并设置超时时间以避免脚本夜间执行暂停时出现错误。


0

这是我使用的:

class KillerThread(threading.Thread):
  def __init__(self, pid, timeout, event ):
    threading.Thread.__init__(self)
    self.pid = pid
    self.timeout = timeout
    self.event = event
    self.setDaemon(True)
  def run(self):
    self.event.wait(self.timeout)
    if not self.event.isSet() :
      try:
        os.kill( self.pid, signal.SIGKILL )
      except OSError, e:
        #This is raised if the process has already completed
        pass    

def runTimed(dt, dir, args, kwargs ):
  event = threading.Event()
  cwd = os.getcwd()
  os.chdir(dir)
  proc = subprocess.Popen(args, **kwargs )
  os.chdir(cwd)
  killer = KillerThread(proc.pid, dt, event)
  killer.start()

  (stdout, stderr) = proc.communicate()
  event.set()      

  return (stdout,stderr, proc.returncode)

0

我猜这是基于事件的多线程和进程编程中常见的同步问题。

如果您始终只有一个子进程在运行,请确保在运行下一个子进程之前杀死当前子进程。否则,信号处理程序可能会引用到上一个子进程并忽略旧的。

假设子进程A正在运行。在处理警报信号之前,启动子进程B。就在那之后,您的警报信号处理程序尝试杀死一个子进程。由于当前PID(或当前子进程管道对象)在启动子进程时设置为B的,因此B被杀死,而A继续运行。

我的猜测正确吗?

为了使您的代码更易于理解,我建议在杀死当前子进程的部分之后包含创建新子进程的部分。这将清楚地表明任何时候都只有一个子进程在运行。信号处理程序可以执行子进程的杀死和启动,就像在循环中运行迭代块一样,在这种情况下是基于事件驱动的,每1秒触发一次警报信号。


0
有点复杂,我添加了一个解决类似问题的答案:捕获标准输出,输入数据并在一段时间内或总运行时间后终止程序。

0

subprocess.run 在 Python 3.5 中添加,这意味着大多数系统应该已经可以访问具有此方法的最新 Python 版本。它使设置 timeout 变得微不足道。

以下是一个示例,使用 --optional args 参数和 60 秒的超时来运行 your-executable(假设它在工作目录中):

import subprocess

subprocess.run(['./your-executable', '--optional', 'args'], timeout=60)

run 会在超时后引发 subprocess.TimeoutExpired,你也可以处理它:

try:
    subprocess.run(['./your-executable', '--optional', 'args'], timeout=60)
except subprocess.TimeoutExpired:
    print('timed out!')

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