轮询子进程对象而不阻塞

时间:2015-09-28 17:38:09

标签: python multithreading subprocess

我正在编写一个python脚本,在后台启动程序,然后监视它们是否遇到错误。我正在使用子进程模块来启动进程并保留正在运行的程序列表。

processes.append((subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE), command))

我发现当我尝试通过调用子进程对象上的通信来监视程序时,主程序等待程序完成。我曾尝试使用poll(),但这并没有让我访问导致崩溃的错误代码,我想解决问题并重试打开该过程。 runningProcesses是包含子进程对象和与之关联的命令的元组列表。

def monitorPrograms(runningProcesses):
    for program in runningProcesses:
        temp = program[0].communicate()
        if program[0].returncode:
            if program[0].returncode == 1:
                print "Program exited successfully."
            else:
                print "Whoops, something went wrong. Program %s crashed." % program[0].pid

当我尝试在不使用通信的情况下获取返回代码时,程序的崩溃没有注册。 我是否必须使用线程并行运行通信,或者是否有一种我丢失的简单方法?

2 个答案:

答案 0 :(得分:2)

无需使用线程来监控多个进程,尤其是在您不使用其输出时(使用DEVNULL instead of PIPE to hide the output),请参阅Python threading multiple bash subprocesses?

您的主要问题是Popen.poll()使用不正确。如果它返回None;这意味着该进程仍在运行 - 您应该调用它直到获得非None值。这与您的案例code example that prints ping processes statuses类似。

如果您确实希望将子进程'stdout / stderr作为字符串,那么您可以使用threads, async.io

如果您使用的是Unix并且控制了可能产生子进程的所有代码,那么您可以避免轮询并自己处理SIGCHLDasyncio stdlib库可以处理SIGCHLD。你也可以implement it manually, though it might be complicated

答案 1 :(得分:1)

根据我的研究,使用线程执行此操作的最佳方法是。我在创建自己的包时引用的Here's an article来解决这个问题。

这里使用的基本方法是旋转不断请求子进程调用的日志输出(最后是退出状态)的线程。

这是我自己"接收器"的一个例子。它监听日志:

class Receiver(threading.Thread):
    def __init__(self, stream, stream_type=None, callback=None):
        super(Receiver, self).__init__()
        self.stream = stream
        self.stream_type = stream_type
        self.callback = callback
        self.complete = False
        self.text = ''

    def run(self):
        for line in iter(self.stream.readline, ''):
            line = line.rstrip()
            if self.callback:
                line = self.callback(line, msg_type=self.stream_type)
            self.text += line + "\n"
        self.complete = True

现在关闭接收器的代码:

 def _execute(self, command):
    process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
                               shell=True, preexec_fn=os.setsid)
    out = Receiver(process.stdout, stream_type='out', callback=self.handle_log)
    err = Receiver(process.stderr, stream_type='err', callback=self.handle_log)
    out.start()
    err.start()
    try:
        self.wait_for_complete(out)
    except CommandTimeout:
        os.killpg(process.pid, signal.SIGTERM)
        raise
    else:
        status = process.poll()
        output = CommandOutput(status=status, stdout=out.text, stderr=err.text)
        return output
    finally:
        out.join(timeout=1)
        err.join(timeout=1)

CommandOutput只是一个命名元组,可以很容易地引用我关心的数据。

您会注意到我有一种方法' wait_for_complete'等待接收器设置完成=真。完成后,execute方法调用process.poll()以获取退出代码。我们现在拥有所有stdout / stderr和进程的状态代码。