如何运行子进程,显示其输出并允许终止?

3 投票
4 回答
3471 浏览
提问于 2025-04-15 14:22

我一直在尝试写一个应用程序,这个程序可以运行子进程,并且(除了其他功能)在图形界面上显示它们的输出,还允许用户点击一个按钮来取消它们。我是这样启动进程的:

queue = Queue.Queue(500)
process = subprocess.Popen(
    command,
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT)
iothread = threading.Thread(
    target=simple_io_thread,
    args=(process.stdout, queue))
iothread.daemon=True
iothread.start()

其中 simple_io_thread 定义如下:

def simple_io_thread(pipe, queue):
    while True:
        line = pipe.readline()
        queue.put(line, block=True)
        if line=="":
            break

这个方法效果还不错。在我的用户界面中,我会定期从队列中非阻塞地获取数据。不过,当我想要终止子进程时,就出现了问题。(这个子进程是一个任意的进程,不是我自己写的。)我可以使用 terminate 方法来结束这个进程,但我不知道怎么确保我的 I/O 线程也会结束。通常情况下,它会在管道上进行阻塞 I/O。这可能会在我终止进程后的一段时间才结束。(如果子进程又启动了一个子进程,我可以杀掉第一个子进程,但第二个子进程仍然会保持管道打开。我甚至不确定怎么让这样的孙子进程干净地结束。)之后,I/O 线程会尝试将输出放入队列,但我不想一直等待从队列中读取数据。

理想情况下,我希望有一种方法可以请求终止子进程,阻塞一小段时间(少于0.5秒),然后确保 I/O 线程已经退出(或者会在合适的时间内退出,不会干扰其他操作),这样我就可以停止从队列中读取数据。

对我来说,使用 I/O 线程并不是必须的。如果有其他方法可以在 Windows 和 Linux 上用 Python 2.6 和 Tkinter GUI 实现这个功能,那也可以。


编辑 - Will 的回答以及我在网上看到的关于其他语言的做法表明,操作系统希望你在主线程中关闭文件句柄,然后 I/O 线程应该会退出阻塞读取。然而,正如我在评论中提到的,这对我来说似乎不管用。如果我在主线程中这样做:

process.stdout.close()

我得到:

IOError: close() called during concurrent operation on the same file object.

...在主线程中。如果我在主线程中这样做:

os.close(process.stdout.fileno())

我得到:

close failed in file object destructor: IOError: [Errno 9] Bad file descriptor

...在主线程稍后尝试自己关闭文件句柄时。

4 个回答

1

在结束进程的代码中,你也可以明确地用 os.close() 来关闭你线程正在读取的管道吗?

2

这可能是个老问题,但对从搜索引擎过来的朋友来说,还是有用的。

出现这个提示的原因是,子进程完成后会关闭文件描述符,因此,正在同时运行的守护线程会尝试使用这些已经关闭的描述符,从而导致错误。

在调用子进程的 wait() 或 communicate() 方法之前,先把线程合并(join)起来,这样就能有效避免这个错误了。

my_thread.join()
print my_thread.is_alive()
my_popen.communicate()
2

我知道这个帖子有点旧了,但如果还有人能从中受益,我觉得你的问题可以通过把subprocess.Popen的实例传给io_thread,而不是它的输出流来解决。

如果这样做的话,你可以把你的while True:这一行替换成while process.poll() == None:

process.poll()这个方法用来检查子进程的返回码;如果进程还没结束,就没有返回码(也就是说process.poll() == None)。这样你就可以去掉if line == "": break这一行了。

我来这里的原因是因为我今天写了一个和这个非常相似的脚本,结果遇到了这些错误:
IOError: close() called during concurrent operation on the same file object.

再说一次,希望能帮到你,我觉得我的问题是因为我的io_thread做了一些过于高效的垃圾回收,关闭了我给它的文件句柄(我可能错了,但现在是可以工作的..)不过我的脚本不同的是,它不是守护进程,而且是通过遍历subprocess.stdout来处理,而不是用while循环.. 也就是说:

def io_thread(subprocess,logfile,lock):
    for line in subprocess.stdout:
        lock.acquire()
        print line,
        lock.release()
        logfile.write( line )

我还应该提一下,我在subprocess.Popen中传递了bufsize参数,这样它就是行缓冲的。

撰写回答