使用os.pipe和os.fork()的Python程序问题

14 投票
4 回答
16909 浏览
提问于 2025-04-15 11:37

最近我需要写一个脚本,使用os.fork()来分裂成两个进程。子进程变成一个服务器进程,通过一个用os.pipe()创建的管道把数据传回父进程。子进程关闭管道的'r'端,父进程关闭'w'端,跟平常一样。我把管道的返回值转换成文件对象,用os.fdopen

我遇到的问题是这样的:进程成功分裂,子进程变成服务器,一切运行得很好,子进程也按规矩把数据写入管道的'w'端。可惜的是,父进程的管道端出现了两个奇怪的情况:
A) 在管道的'r'端进行read()操作时,它会被阻塞。
其次,父进程无法读取任何放入管道的数据,除非'w'端完全关闭。

我立刻想到可能是缓冲的问题,于是添加了pipe.flush()的调用,但这些并没有帮助。

有没有人能解释一下为什么数据在写入端完全关闭之前不会出现?还有没有什么方法可以让read()调用不被阻塞?

这是我写的第一个使用分叉和管道的Python程序,如果我犯了简单的错误,请多多包涵。

4 个回答

5

我看到你已经解决了阻塞输入输出和缓冲的问题。

如果你决定尝试不同的方法,有一点需要注意:subprocess就相当于fork/exec的替代方案。看起来你现在的做法并不是这样:你只是进行了fork(没有exec),并在两个进程之间交换数据——在这种情况下,使用multiprocessing模块(在Python 2.6及以上版本中)会更合适。

6

使用

fcntl.fcntl(readPipe, fcntl.F_SETFL, os.O_NONBLOCK)

在调用read()之前,这样做解决了两个问题。现在,read()这个操作不再是阻塞的,而且在写入端只要调用flush(),数据就能立刻出现。

14

你是不是在用read()的时候没有指定大小,或者把管道当成迭代器来用(比如用for line in f)?如果是这样,那可能就是你遇到问题的原因——read()的定义是要读取到文件的末尾才会返回,而不是只读取当前可用的数据。这就意味着它会一直等待,直到子进程调用close()。

在链接的示例代码中,这样做是可以的——父进程是以阻塞的方式运行,只是把子进程当作隔离的工具。如果你想继续进行,可以使用非阻塞的输入输出,就像你发的代码那样(但要准备好处理不完整的数据),或者分块读取(比如用r.read(size)或者r.readline()),这样只会在读取到特定大小或一行数据之前阻塞一下。(你仍然需要在子进程上调用flush)

看起来把管道当成迭代器使用时,也会使用一些额外的缓冲区,因为如果你需要每一行都能立即被消费,for line in r:可能不会给你想要的结果。可能可以禁用这个功能,但在fdopen中将缓冲区大小指定为0似乎不够。

这里有一些应该能工作的示例代码:

import os, sys, time

r,w=os.pipe()
r,w=os.fdopen(r,'r',0), os.fdopen(w,'w',0)

pid = os.fork()
if pid:          # Parent
    w.close()
    while 1:
        data=r.readline()
        if not data: break
        print "parent read: " + data.strip()
else:           # Child
    r.close()
    for i in range(10):
        print >>w, "line %s" % i
        w.flush()
        time.sleep(1)

撰写回答