子流程的性能。检查输出与子流程。

2024-04-27 05:25:22 发布

您现在位置:Python中文网/ 问答频道 /正文

我已经使用subprocess.check_output()一段时间来捕获子进程的输出,但是在某些情况下遇到了一些性能问题。我在RHEL6机器上运行。

调用Python的环境是linux编译的64位环境。我正在执行的子进程是一个shell脚本,它最终通过Wine触发一个Windows python.exe进程(为什么需要这种愚蠢是另一回事)。作为shell脚本的输入,我正在输入一小段传递给Python.exe的Python代码。

当系统处于中等/高负载(40%到70%的CPU利用率)时,我注意到使用subprocess.check_output(cmd, shell=True)可能会导致子进程在check_output命令返回之前完成执行后出现明显的延迟(最多45秒)。在此期间查看来自ps -efH的输出,将调用的子进程显示为sh <defunct>,直到它最终以正常的零退出状态返回。

相反,使用subprocess.call(cmd, shell=True)在相同的中/重载下运行相同的命令将导致子进程立即返回,并且没有延迟,所有输出都打印到STDOUT/STDERR(而不是从函数调用返回)。

为什么只有当check_output()将STDOUT/STDERR输出重定向到其返回值时才会有如此大的延迟,而不是当call()简单地将其打印回父级的STDOUT/STDERR时呢?


Tags: 命令脚本cmdtrueoutput环境进程check
2条回答

阅读文档时,subprocess.callsubprocess.check_output都是subprocess.Popen的用例。一个小的区别是,如果子进程返回非零退出状态,check_output将引发Python错误。关于check_output(我的重点)的部分强调了更大的差异:

The full function signature is largely the same as that of the Popen constructor, except that stdout is not permitted as it is used internally. All other supplied arguments are passed directly through to the Popen constructor.

那么stdout是如何“内部使用”的呢?让我们比较一下callcheck_output

呼叫

def call(*popenargs, **kwargs):
    return Popen(*popenargs, **kwargs).wait() 

检查输出

def check_output(*popenargs, **kwargs):
    if 'stdout' in kwargs:
        raise ValueError('stdout argument not allowed, it will be overridden.')
    process = Popen(stdout=PIPE, *popenargs, **kwargs)
    output, unused_err = process.communicate()
    retcode = process.poll()
    if retcode:
        cmd = kwargs.get("args")
        if cmd is None:
            cmd = popenargs[0]
        raise CalledProcessError(retcode, cmd, output=output)
    return output

沟通

现在我们还要看Popen.communicate。这样做,我们注意到对于一个管道,communicate所做的几件事比像call那样简单地返回Popen().wait()所花费的时间要长。

首先,communicate处理stdout=PIPE,不管您是否设置了shell=True。显然,call没有。它只会让你的壳喷出任何东西。。。使之成为一种安全风险,as Python describes here

其次,在check_output(cmd, shell=True)(只有一个管道)的情况下。。。子进程发送到stdout的任何内容都由_communicate方法中的线程处理。并且Popen必须加入线程(等待它),然后再等待子进程本身终止!

另外,更简单的是,它将stdout处理为list,然后必须将其连接到字符串中。

简而言之,即使参数最小,check_output在Python进程中花费的时间也比call要多得多。

让我们看看代码。.check_输出有以下等待:

    def _internal_poll(self, _deadstate=None, _waitpid=os.waitpid,
            _WNOHANG=os.WNOHANG, _os_error=os.error, _ECHILD=errno.ECHILD):
        """Check if child process has terminated.  Returns returncode
        attribute.

        This method is called by __del__, so it cannot reference anything
        outside of the local scope (nor can any methods it calls).

        """
        if self.returncode is None:
            try:
                pid, sts = _waitpid(self.pid, _WNOHANG)
                if pid == self.pid:
                    self._handle_exitstatus(sts)
            except _os_error as e:
                if _deadstate is not None:
                    self.returncode = _deadstate
                if e.errno == _ECHILD:
                    # This happens if SIGCLD is set to be ignored or
                    # waiting for child processes has otherwise been
                    # disabled for our process.  This child is dead, we
                    # can't get the status.
                    # http://bugs.python.org/issue15756
                    self.returncode = 0
        return self.returncode

.call使用以下代码等待:

    def wait(self):
        """Wait for child process to terminate.  Returns returncode
        attribute."""
        while self.returncode is None:
            try:
                pid, sts = _eintr_retry_call(os.waitpid, self.pid, 0)
            except OSError as e:
                if e.errno != errno.ECHILD:
                    raise
                # This happens if SIGCLD is set to be ignored or waiting
                # for child processes has otherwise been disabled for our
                # process.  This child is dead, we can't get the status.
                pid = self.pid
                sts = 0
            # Check the pid and loop as waitpid has been known to return
            # 0 even without WNOHANG in odd situations.  issue14396.
            if pid == self.pid:
                self._handle_exitstatus(sts)
        return self.returncode

请注意,这个bug与内部轮询有关。可在http://bugs.python.org/issue15756查看。几乎就是你遇到的问题。


编辑:在.call和.check_输出之间的另一个潜在问题是.check_输出实际上关心stdin和stdout,并将尝试对这两个管道执行IO。如果您正在运行的进程本身进入僵尸状态,则可能是对处于失效状态的管道的读取导致了您正在经历的挂起。

在大多数情况下,僵尸状态会很快被清除,但是,如果它们在系统调用(比如读或写)时被中断,它们就不会被清除。当然,一旦IO不能再执行,读/写系统调用本身就应该被中断,但是,有可能您遇到了某种竞争条件,在这种情况下,事情会以错误的顺序被终止。

在这种情况下,我能想到的唯一确定原因的方法是,要么向子进程文件中添加调试代码,要么调用python调试器,并在遇到所遇到的情况时启动回溯。

相关问题 更多 >