使用Python的select模块检查是否有更多数据要从文件描述符读取

使用Python的select模块检查是否有更多数据要从文件描述符读取,python,linux,select,subprocess,Python,Linux,Select,Subprocess,我有一个在线程中创建子进程的程序,因此线程可以不断检查stdout或stderr中的特定输出条件,并在程序的其余部分继续时调用相应的回调。下面是该代码的精简版本: import select import subprocess import threading def run_task(): command = ['python', 'a-script-that-outputs-lines.py'] proc = subprocess.Popen(command, stdout

我有一个在线程中创建子进程的程序,因此线程可以不断检查stdout或stderr中的特定输出条件,并在程序的其余部分继续时调用相应的回调。下面是该代码的精简版本:

import select
import subprocess
import threading

def run_task():
    command = ['python', 'a-script-that-outputs-lines.py']
    proc = subprocess.Popen(command, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
    while True:

        ready, _, _ = select.select((proc.stdout, proc.stderr), (), (), .1)

        if proc.stdout in ready:
            next_line_to_process = proc.stdout.readline()
            # process the output

        if proc.stderr in ready:
            next_line_to_process = proc.stderr.readline()
            # process the output

        if not ready and proc.poll() is not None:
            break

thread = threading.Thread(target = run_task)
thread.run()
它工作得相当好,但我希望线程在满足两个条件后退出:正在运行的子进程已经完成,stdout和stderr中的所有数据都已经处理

我遇到的困难是,如果我的最后一个条件与上面的条件相同,如果没有就绪,并且proc.poll不是None,那么线程永远不会退出,因为一旦stdout和stderr的文件描述符标记为就绪,即使从它们读取了所有数据,它们也永远不会变为未就绪,read将挂起,或者readline将返回一个空字符串

如果我将该条件更改为just If proc.poll不是None,那么当程序退出时循环就存在了,我不能保证它看到了所有需要处理的数据

这是一种错误的方法,还是有一种方法可以可靠地确定您何时读取了将要写入文件描述符的所有数据?或者这是一个特定于尝试从子流程的stderr/stdout读取的问题

我已经在运行OS X的Python 2.5上尝试了这一点,还尝试了运行Debian 2.6内核的Python 2.6上基于select.poll和select.epoll的变体。

您可以在管道的文件描述符上执行原始OS.readfd,而不是使用readline。这是一个非阻塞操作,也可以检测EOF,在这种情况下,它返回一个空字符串或字节对象。您必须自己实现行分割和缓冲。使用类似以下内容:

class NonblockingReader():
  def __init__(self, pipe):
    self.fd = pipe.fileno()
    self.buffer = ""

  def readlines(self):
    data = os.read(self.fd, 2048)
    if not data:
      return None

    self.buffer += data
    if os.linesep in self.buffer:
      lines = self.buffer.split(os.linesep)
      self.buffer = lines[-1]
      return lines[:-1]
    else:
      return []

如上所述,我的最终解决方案如下,以防这对任何人都有帮助。我认为这是一种正确的方法,因为我现在97.2%确定你不能只选择/投票并阅读:

如果您想确定是否可以在不阻塞的情况下读取管道,则选择模块是合适的

为了确保您已经读取了所有数据,如果proc.poll不是None,请使用一个更简单的条件:在循环之后中断并调用rest=[p.stdout,p.stderr]]中的管道的[pipe.read]

子进程不太可能在关闭之前关闭其stdout/stderr,因此为了简单起见,可以跳过处理EOF的逻辑

不要直接调用Thread.run,而是使用Thread.start。这里可能根本不需要单独的线程

选择后不要调用p.stdout.readline,它可能会阻塞,请使用os.readp.stdout.fileno,改为limit。空bytestring表示对应管道的EOF

作为替代或补充,您可以使用fcntl模块使管道不堵塞:


并在读取时处理io/os错误。

我当前运行的解决方案是,在调用readline后立即测试下一个\u-line\u-to\u进程是否为空,如果为空且proc.poll的输出为无,则从就绪列表中删除文件描述符,然后继续下一个,或者查看是否该退出。但是我很想知道是否还有其他解决方案。在这种情况下,阻塞/非阻塞行为不是什么大问题,因为我没有两个线程从同一个文件描述符读取。但是,当os.read到达文件末尾时,它返回一个空字符串,而不是None:没错,在2.x中它是空字符串。但我认为在更新的3.x版本中应该没有,我不这么认为。在Python3中,它似乎是一个空字节对象:谢谢,这非常有用。我不知道FCNTL模块。子进程在关机之前确实关闭了它的文件描述符。@ Ssivnn,进程在执行过程中关闭它的STDUT是不太可能的,也就是说,我可能不知道任何真实世界的例子。
import select
import subprocess
import threading

def run_task():
    command = ['python', 'a-script-that-outputs-lines.py']
    proc = subprocess.Popen(command, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
    while True:

        ready, _, _ = select.select((proc.stdout, proc.stderr), (), (), .1)

        if proc.stdout in ready:
            next_line_to_process = proc.stdout.readline()
            if next_line_to_process:
                # process the output
            elif proc.returncode is not None:
                # The program has exited, and we have read everything written to stdout
                ready = filter(lambda x: x is not proc.stdout, ready)

        if proc.stderr in ready:
            next_line_to_process = proc.stderr.readline()
            if next_line_to_process:
                # process the output
            elif proc.returncode is not None:
                # The program has exited, and we have read everything written to stderr
                ready = filter(lambda x: x is not proc.stderr, ready)

        if proc.poll() is not None and not ready:
            break

thread = threading.Thread(target = run_task)
thread.run()
import os
from fcntl import fcntl, F_GETFL, F_SETFL

def make_nonblocking(fd):
    return fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | os.O_NONBLOCK)