我正在使用子进程模块启动子进程并连接到它的输出流(stdout)。 我希望能够在其标准输出上执行非阻塞读取。 有没有办法让.readline非阻塞或在我调用.readline之前检查流上是否有数据? 我希望这是可移植的,或至少在Windows和Linux下工作。
这是我现在的工作方式(如果没有数据可用,它会在.readline上阻塞):
p = subprocess.Popen('myprogram.exe', stdout = subprocess.PIPE)
output_str = p.stdout.readline()
(来自谷歌?)当其中一个PIPE的缓冲区被填满而未被读取时,所有PIPE都会死锁。 例如 当stderr被填满时stdout死锁。 永远不要传递你不打算阅读的PIPE。
@ NasserAl-Wohaibi这是否意味着它总能更好地创建文件呢?
我一直很想知道的是为什么它首先阻塞...我问,因为我看到了评论:To avoid deadlocks: careful to: add
to output, flush output, use readline() rather than read()
它是"按设计"等待接收输入。
相关:stackoverflow.com/q/19880190/240515
在这种情况下,fcntl,select,asyncproc将无济于事。
无论操作系统如何,无阻塞地读取流的可靠方法是使用Queue.get_nowait():
import sys
from subprocess import PIPE, Popen
from threading import Thread
try:
from queue import Queue, Empty
except ImportError:
from Queue import Queue, Empty # python 2.x
ON_POSIX = 'posix' in sys.builtin_module_names
def enqueue_output(out, queue):
for line in iter(out.readline, b''):
queue.put(line)
out.close()
p = Popen(['myprogram.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()
# ... do other things here
# read line without blocking
try: line = q.get_nowait() # or q.get(timeout=.1)
except Empty:
print('no output yet')
else: # got line
# ... do something with line
是的,这对我有用,我删除了很多。它包括良好实践,但并非总是必要的。 Python 3.x 2.X compat和close_fds可能会被省略,它仍然有效。但只要知道一切都做了什么,不要盲目地复制它,即使它只是有效! (实际上最简单的解决方案是使用一个线程并像Seb那样做一个readline,Qeues只是获取数据的简单方法,还有其他的,线程就是答案!)
在线程内部,对out.readline的调用会阻塞线程和主线程,我必须等到readline返回,然后其他所有内容才会继续。有什么简单的方法吗? (我正在从我的进程中读取多行,这也是另一个正在执行数据库和事物的.py文件)
@Justin:'out.readline'不会阻止它在另一个线程中执行的主线程。
close_fds绝对不是你想盲目复制到你的应用程序中的东西......
如果我无法关闭子进程,例如。由于例外? stdout-reader线程不会死,python会挂起,即使主线程退出,不是吗?怎么可以解决这个问题呢? python 2.x不支持杀死线程,更糟糕的是,不支持中断它们。 :((显然应该处理异常以确保子进程被关闭,但万一它不会,你能做什么?)
@naxa:notice daemon=True:如果退出主线程,python进程将不会挂起。
我在包shelljob pypi.python.org/pypi/shelljob中创建了一些友好的包装器
我在一个单独的线程中启动了Popen,所以我可以在这个线程中"忙等",使用:.... t.start() while p.poll() == None: time.sleep(0.1)在这种情况下,我的GUI不会阻塞(我正在使用TKinter)。所以我也可以使用parent.after(100, self.consume)来模拟事件类型的轮询。在consume方法中,我最终使用q.get()方法从队列中检索数据。奇迹般有效!虽然有些人说你可以使用wait()或poll()与stdout PIPE组合进行死锁?
@ danger89:是的。如果您的父级在.wait()上被阻止但您的孩子等待您阅读其输出(不是您的情况,但GUI回调中的q.get()也不正确),您可能会死锁。最简单的选择是在单独的线程中使用.communicate()。下面是一些代码示例,展示了如何在不"锁定"GUI的情况下从子进程读取输出:1。使用线程2.无线程(POSIX)。如果有什么不清楚,请问
这里close_fds的目的是什么?
@dashesy:避免泄漏父文件描述符。演示的行为是Python 3的默认行为。
@ J.F.Sebastian:有道理。这是唯一没有阻止它的解决方案,似乎在2.7没有其他办法(尝试选择,fcntl)!知道为什么我无法从sgid进程获取输出吗?它在终端工作得很好,但在这里我得到no output yet,这是不可能的。
@dashesy:select,fcntl应该适用于POSIX系统。如果你不明白为什么"没有输出"总是有可能给出给定的解决方案;问一个新问题。请务必阅读如何询问和stackoverflow.com/help/mcve
@ J.F.Sebastian对不起我的绝望尝试,我想你可能已经知道了答案,你看起来像你做的:)我下次会问一个新问题。 BTW,在可执行文件中使用setbuf(stdout, NULL)之后(大多数工作甚至选择和fcntl)就像魅力一样,我不知道为什么没有刷新标准输出但我不会感到惊讶,如果它与它有关suid和selinux。
@dashesy:如果子进程'stdout被重定向到一个管道,那么它是块缓冲的(如果stdout是终端(tty),它是行缓冲的),用于用C编写的基于stdio的程序。参见Python C程序子进程挂起"for it in iter"
@ J.F.Sebastian不知道这一点,天真地我总是假设在tty中看到的行为,但现在它是有道理的。因此,这意味着智能应用程序应该查看stdout的类型,如果它们生成的东西是行并且可以在pipes / grep中使用。
你在enqueue_output中做out.close()的原因是什么?不应该是Popen对象的工作吗?
@zaphod:完成同样的原因with -statement用于普通文件:避免依赖难以理解的垃圾收集来释放资源,即使p.stdout在是垃圾收集:我更喜欢显式的简单和确定性out.close()(虽然我应该在线程中使用with out: - 它似乎适用于Python 2.7和Python 3)。
该解决方案不适用于可能代码系统(16-48核心系统)。 GIL在上下文切换中发挥作用。
如下所述,更好地使用非阻塞IO。
@AntonMedvedev:1。无论有多少CPU都没关系。问题是关于I / O(Python在阻止I / O操作期间发布GIL)。 2.在答案时,便携式解决方案无法替代stdlib中的线程。非阻塞IO是否比线程更好(无条件地)是有争议的。一种明智的方法是研究特定情况下的权衡。
你写'Windows和Linux',这是否排除了OSX?我刚刚在OSX上尝试执行ffmpeg,这似乎有问题。我将更详细地测试这个,除非你告诉我这在OSX中不起作用。
@ P.R。:OP询问这些操作系统,这就是明确提到它们的原因。它也适用于OS X.
@nights:"不适合我"并不是很有用。创建一个最小的代码示例,使用单词描述您期望得到什么以及您逐步获得什么,您是什么操作系统,Python版本并将其作为单独的问题发布。
不要为多处理和多处理切换线程。有这个答案的问题。子进程的终止导致stdout光标移动到我的屏幕的开头。
@jfs是非阻塞I / O必须是异步的吗?
@ uzay95我不确定你在问什么。这些概念密切相关。答案显示了如何在同步阻塞readline()调用之上实现非阻塞读取。该接口也是异步的(执行IO时可能会发生其他事情)。
我觉得我每个月左右偶然发现一次这个帖子......我仍然不知道为什么声称"fcntl在这种情况下不会帮助"。我现在必须使用fnctl设置os.O_NONBLOCK(使用os.read(),而不是readline())实现10-20次。它似乎通常按预期工作。
@cheshirekow是否支持Windows fcntl? Python 2上的readline()是否支持非阻塞模式?
@jfs不确定windows ...你知道吗?这就是为什么你说它不会有帮助?另请注意,我用os.read()而不是readline()说。
os.read()在Windows上可用,但os.O_NONBLOCK被记录为特定于Unix,因此我不希望它在那里工作。
当我尝试使用提供的逻辑回答stderr数据无法得到它
看起来stderr数据也会转到stdout
@Karthi1234没有,stdout在回答管道。 stderr不在这里去管道。你的代码是另一回事。
有没有提到或使用docs.python.org/3/library/asyncio-api-index.html库的原因?这不好吗?
@CharlieParker向下滚动到我的另一个答案
我经常遇到类似的问题;我经常编写的Python程序需要能够执行一些主要功能,同时从命令行(stdin)接受用户输入。简单地将用户输入处理功能放在另一个线程中并不能解决问题,因为readline()会阻塞并且没有超时。如果主要功能已完成并且不再需要等待进一步的用户输入,我通常希望我的程序退出,但它不能,因为readline()仍然在等待一行的另一个线程中阻塞。我发现这个问题的解决方案是使用fcntl模块使stdin成为非阻塞文件:
import fcntl
import os
import sys
# make stdin a non-blocking file
fd = sys.stdin.fileno()
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
# user input handling thread
while mainThreadIsRunning:
try: input = sys.stdin.readline()
except: continue
handleInput(input)
在我看来,这比使用选择或信号模块解决这个问题要清晰一点,但是它再次只适用于UNIX ...
根据文档,fcntl()可以接收文件描述符或具有.fileno()方法的对象。
杰西的回答是不正确的。根据Guido的说法,readline在非阻塞模式下无法正常工作,并且它不会在Python 3000之前正常工作.bugs.python.org/issue1175#msg56041如果你想使用fcntl将文件设置为非阻塞模式,你必须使用较低级别的os.read()并自己分开行。将fcntl与执行行缓冲的高级调用混合会产生麻烦。
在Python 2中使用readline似乎不正确。请参阅anonnn的回答stackoverflow.com/questions/375427/…
请不要使用繁忙的循环。使用poll()和超时来等待数据。
用read(buffer_size)替换readline似乎完美无缺...
@Stefano什么是buffer_size定义为?
@cat:您的选择,我通常有1024 - 它是一次读取的字节数,所以根据您的预期数据大小设置它更小或更大!
@Stefano是的,我没有意识到它可能是任意文字
有没有提到或使用docs.python.org/3/library/asyncio-api-index.html库的原因?这不好吗?
Python 3.4为异步IO - asyncio模块引入了新的临时API。
该方法类似于@Bryan Ward基于twisted的回答 - 定义协议,并在数据准备就绪后调用其方法:
#!/usr/bin/env python3
import asyncio
import os
class SubprocessProtocol(asyncio.SubprocessProtocol):
def pipe_data_received(self, fd, data):
if fd == 1: # got stdout data (bytes)
print(data)
def connection_lost(self, exc):
loop.stop() # end loop.run_forever()
if os.name == 'nt':
loop = asyncio.ProactorEventLoop() # for subprocess' pipes on Windows
asyncio.set_event_loop(loop)
else:
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(loop.subprocess_exec(SubprocessProtocol,
"myprogram.exe","arg1","arg2"))
loop.run_forever()
finally:
loop.close()
请参阅文档中的"子流程"。
有一个高级接口asyncio.create_subprocess_exec()返回Process对象,允许使用StreamReader.readline()协同程序异步读取一行
(使用async / await Python 3.5+语法):
#!/usr/bin/env python3.5
import asyncio
import locale
import sys
from asyncio.subprocess import PIPE
from contextlib import closing
async def readline_and_kill(*args):
# start child process
process = await asyncio.create_subprocess_exec(*args, stdout=PIPE)
# read line (seq