Python:如何查看pty对象以避免阻塞?

5 投票
2 回答
5175 浏览
提问于 2025-04-16 19:58

我正在使用 pty 来非阻塞地读取一个进程的标准输出,代码大致是这样的:

import os
import pty
import subprocess

master, slave = pty.openpty()

p = subprocess.Popen(cmd, stdout = slave)

stdout = os.fdopen(master)
while True:
    if p.poll() != None:
        break

    print stdout.readline() 

stdout.close()

一切运行得很好,除了有时候 while-loop 会卡住。这是因为 print stdout.readline() 这一行在等着从 stdout 读取数据。但是如果程序已经结束了,我的这个小脚本就会一直卡在那里。

我的问题是:有没有办法先查看一下 stdout 对象,看看有没有数据可以读取?如果没有数据,就应该继续执行 while-loop,这样它就能发现进程其实已经结束了,然后跳出循环。

2 个回答

2

这个select.poll()的回答很不错,但在Windows上不适用。下面这个解决方案是个替代方案。虽然它不能让你查看标准输出,但提供了一种非阻塞的方式来替代readline(),并且是基于这个回答的:

from subprocess import Popen, PIPE
from threading import Thread
def process_output(myprocess): #output-consuming thread
    nextline = None
    buf = ''
    while True:
        #--- extract line using read(1)
        out = myprocess.stdout.read(1)
        if out == '' and myprocess.poll() != None: break
        if out != '':
            buf += out
            if out == '\n':
                nextline = buf
                buf = ''
        if not nextline: continue
        line = nextline
        nextline = None

        #--- do whatever you want with line here
        print 'Line is:', line
    myprocess.stdout.close()

myprocess = Popen('myprogram.exe', stdout=PIPE) #output-producing process
p1 = Thread(target=process_output, args=(myprocess,)) #output-consuming thread
p1.daemon = True
p1.start()

#--- do whatever here and then kill process and thread if needed
if myprocess.poll() == None: #kill process; will automatically stop thread
    myprocess.kill()
    myprocess.wait()
if p1 and p1.is_alive(): #wait for thread to finish
    p1.join()

还有其他非阻塞读取的解决方案在这里被提出过,但对我来说都不管用:

  1. 那些需要使用readline的解决方案(包括基于队列的)总是会阻塞。要终止执行readline的线程很困难(甚至是不可能的)。这个线程只有在创建它的进程结束时才会被杀掉,而不是在产生输出的进程被杀掉时。
  2. 将低级的fcntl和高级的readline调用混合使用,可能会出现问题,正如anonnn所指出的那样。
  3. 使用select.poll()很不错,但根据Python文档,它在Windows上不适用。
  4. 使用第三方库似乎对这个任务来说有点过于复杂,还增加了额外的依赖。
11

是的,可以使用 select模块中的poll

import select
q = select.poll()
q.register(stdout,select.POLLIN)

然后在循环中使用:

l = q.poll(0)
if not l:
    pass # no input
else:
    pass # there is some input

撰写回答