需求是使用 subprocess 执行一个耗时命令,对终端的输出进行非阻塞的实时监控,当终端输出 error 信息时,关闭 Popen ,或者执行一段时间后自动关闭 Popen 。因为同时需要实时来监控多个终端输出,需要非阻塞。我依次使用了以下两种方法:
1 、 最初的实现为使用 asyncio 创建一个 loop 并且 run_forever() ,然后将读取 Popen 输出的阻塞方法( While Popen is None )注册到该 loop 上运行,读取 stdout 输出,如果没有检测到 error 信息,就 asyncio.sleep(0.01);后来我发现当存在多个 Popen 的时候,sleep 切换会导致有的终端输出读取丢失,并不能实时读取到,所以我采用第二种方法;
2 、 第二次实现是给每个 Popen 创建一个线程,使用线程的 daemon start 来实时监控,资源消耗会比 1 大但是目前还没有遇到读取实时监控的问题。
然后又有一个需求,如果用户想要中途终止该进行监控的 subprocess 的 Popen ,该如何中途停止该 Popen 呢?
另求:各路大神有没有比上述方案 1 和方案 2 中更适合的实现非阻塞实时读取输出的方案?
1
ysc3839 2022-04-27 10:37:45 +08:00
我不了解 Python 。但是异步框架一般有读 fd 的功能吧?同时读所有管道对应的 fd 就好了。
|
2
julyclyde 2022-04-27 11:01:24 +08:00
不能实时还是不能读到?
|
3
LeeReamond 2022-04-27 11:10:26 +08:00
看了一下以前自己的实现,subprocess.Popen 是同步代码,肯定是不在事件循环的线程里执行的,创建子进程后用 process.stdout.fileno()拿到 stdout ,然后用 selectors.DefualtSelector 挂上,印象里这个 default 在 linux 下是会自动用 epoll ,推流用 call_soon_threadsafe 就推回事件循环了。关闭子进程用的是三方库 psutil ,selector 挂起无法自动关闭,解决方法是加个 timeout ,最慢 10 秒内关闭。
|
4
kayseen OP @ysc3839 我曾经试过通过管道的 fd 来读取,但是也会因为 while Popen is None 阻塞线程,也有可能是我的使用方式不对
|
5
kayseen OP @julyclyde 感谢回复。这是针对使用 asyncio 的 loop 实现的问题,我曾经复现过,是因为开启多个 popen 的时候,通过 asyncio.sleep 来切换读取其他的 popen ,如果一个 popen 此时输出了 error ,然后自动关闭了 popen ,因为 loop 此时切换到了其他的 popen 读取,就会丢失从 error 到关闭的范围内的输出
|
6
kayseen OP @LeeReamond 感谢回复,思考了好一会,感觉思路挺清晰,大多是没有接触过的东西,要好好消化一会儿 qaq 。。。
|
7
wwqgtxx 2022-04-27 13:36:10 +08:00 2
asyncio 本来不就有对 popen 的支持么
https://docs.python.org/3/library/asyncio-subprocess.html https://docs.python.org/3/library/asyncio-eventloop.html#running-subprocesses 多开几个 task 去用你的第二次实现的思路不就得了 |
8
lolizeppelin 2022-04-27 13:56:13 +08:00
linux 下用 fork exec 加 select 管道去折腾
折腾熟了 subprocess 什么回事自然就知道了 协程的话 eventlet 里有 GreenPile ,asyncio 里应该也有类似封装 |