Python逐行从子流程捕获stdout

发布于 2021-01-29 16:13:07

我已经阅读了许多与此相关的问题并学到了很多东西,但是我仍然无法解决我的问题。我正在构建一个运行c
++可执行文件并实时显示该可执行文件的stdout的wxPython应用。我在尝试进行这项工作时遇到了一些奇怪的结果。这是我当前的设置/问题:

//test.cc (compiled as test.out with gcc 4.5.2)
#include <stdio.h>
int main()
{
  FILE* fh = fopen("output.txt", "w");
  for (int i = 0; i < 10000; i++)
  {
      printf("Outputting: %d\n", i);
      fprintf(fh, "Outputting: %d\n", i);
  }
  fclose(fh);
  return 0;
}

#wxPythonScript.py (running on 2.7 interpreter)
def run(self):
  self.externalBinary = subprocess.Popen(['./test.out'], shell=False, stdout=subprocess.PIPE)
  while not self.wantAbort:
      line = self.externalBinary.stdout.readline()
      wx.PostEvent(self.notifyWindow, Result_Event(line, Result_Event.EVT_STDOUT_ID))
    print('Subprocess still running')
  print('Subprocess aborted smoothly')

如果我运行上述代码,则子过程将花费很长时间才能完成,即使它要做的只是写出数据并退出。但是,如果我运行以下命令,它将很快完成:

#wxPythonScript.py (running on 2.7 interpreter)
def run(self):
  outFile = open('output.txt', 'r+')
  self.externalBinary = subprocess.Popen(['./test.out'], shell=False, stdout=outFile)
  while not self.wantAbort:
      #line = self.externalBinary.stdout.readline()
      #wx.PostEvent(self.notifyWindow, Result_Event(line, Result_Event.EVT_STDOUT_ID))
    print('Subprocess still running')
  print('Subprocess aborted smoothly')

因此,基本上,每当我将stdout从子进程重定向到PIPE时,它都会减慢/挂起,但是如果我将其写入文件或根本不重定向它,那就很好了。这是为什么?

关注者
0
被浏览
52
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

    我仅在Windows上进行了测试,但是它可以在2.6.6、2.7.2和3.2.1中运行:

    from __future__ import print_function
    from subprocess import PIPE, Popen
    from threading  import Thread
    import sys
    
    try:
        from Queue import Queue, Empty
    except ImportError:
        from queue import Queue, Empty  # python 3.x
    
    ON_POSIX = 'posix' in sys.builtin_module_names
    
    def enqueue_output(out, queue):
        for line in iter(out.readline, b''):
            line = line.decode(sys.stdout.encoding)
            queue.put(line)
        out.close()
    
    def main():
        p = Popen(['c/main.exe'], stdout=PIPE, bufsize=1, close_fds=ON_POSIX)
        q = Queue()
        t = Thread(target=enqueue_output, args=(p.stdout, q))
        t.daemon = True # thread dies with the program
        t.start()
    
        #initially the queue is empty and stdout is open
        #stdout is closed when enqueue_output finishes
        #then continue printing until the queue is empty
    
        while not p.stdout.closed or not q.empty():
            try:
                line = q.get_nowait()
            except Empty:
                continue
            else:
                print(line, end='')
        return 0
    
    if __name__ == '__main__':
        sys.exit(main())
    

    输出:

    Outputting: 0
    Outputting: 1
    Outputting: 2
    ...
    Outputting: 9997
    Outputting: 9998
    Outputting: 9999
    

    编辑:

    readline()将阻塞,直到刷新程序的标准输出缓冲区为止,如果数据流是间歇性的,则可能需要很长时间。如果您可以编辑源代码,则一种方法是手动调用fflush(stdout),也可以在程序开始时使用setvbuf禁用缓冲。例如:

    #include <stdio.h>
    
    int main() {
    
        setvbuf(stdout, NULL, _IONBF, 0);
    
        FILE* fh = fopen("output.txt", "w");
        int i;
    
        for (i = 0; i < 10; i++) {
            printf("Outputting: %d\n", i);
            fprintf(fh, "Outputting: %d\n", i);
            sleep(1);
        }
    
        fclose(fh);
        return 0;
    }
    

    还要考虑使用unbufferstdbuf修改现有程序的输出流。



知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看