Python和FIFOs

6
我正在尝试在Linux下使用Python理解FIFO,但是发现了一种奇怪的行为,我不太理解。
以下是fifoserver.py的内容:
import sys
import time

def readline(f):
    s = f.readline()
    while s == "":
        time.sleep(0.0001)
        s = f.readline()
    return s

while True:
    f = open(sys.argv[1], "r")
    x = float(readline(f))
    g = open(sys.argv[2], "w")
    g.write(str(x**2) + "\n")
    g.close()
    f.close()
    sys.stdout.write("Processed " + repr(x) + "\n")

以下是fifoclient.py代码。

import sys
import time

def readline(f):
    s = f.readline()
    while s == "":
        time.sleep(0.0001)
        s = f.readline()
    return s

def req(x):
    f = open("input", "w")
    f.write(str(x) + "\n")
    f.flush()
    g = open("output", "r")
    result = float(readline(g))
    g.close()
    f.close()
    return result

for i in range(100000):
    sys.stdout.write("%i, %s\n" % (i, i*i == req(i)))

我还使用mkfifo inputmkfifo output创建了两个FIFO。
我不明白的是,为什么当我在两个控制台中运行服务器(使用python fifoserver.py input output)和客户端(使用python fifoclient.py)进行一些请求后,客户端会因f.flush()出现“broken pipe”错误而崩溃。请注意,在崩溃之前,我已经看到了几百个到数千个正确处理的请求正在运行良好。
我的代码有什么问题?

我认为你在这里复制粘贴时出现了一个错误。客户端和服务器端的代码是相同的。 - Jeff Bauer
@Jeff Bauer:抱歉……我想我永远也不会习惯 X 中这种愚蠢的剪贴板处理方式。 - 6502
3
你的服务器泄露文件句柄:每次循环迭代,你都会打开一个新的文件句柄到 sys.argv[2] ,但从未关闭。不要假设垃圾回收器会为你处理它 -- 用调用 close() 显式清理,或更好地使用 with 语句。 - Adam Rosenfield
你的客户端也因为同样的原因泄漏了文件句柄。 - Adam Rosenfield
1
@Adam:文件会自动关闭,因为赋值f = open(...)使用名称f来创建一个新的文件对象,从而丢弃了旧对象的最后一个引用。旧的文件对象立即被垃圾回收,这包括它被关闭。 - Sven Marnach
1
@Adam Rosenfield(和其他两位点赞者):不是的。我使用的是一个不需要显式关闭的Python版本,但即使添加了它也无法解决问题。现在代码关闭了句柄,并且与python2.x和python3.x兼容。但问题仍然存在于两个版本中。 - 6502
2个回答

5

正如其他评论所提到的,您遇到了竞态条件。

我怀疑在失败的情况下,服务器在以下某一行之后被挂起:

g.write(str(x**2) + "\n")
g.close()

客户端可以读取结果并将其打印到屏幕上,然后循环回去。接着,它重新打开 f - 这是成功的,因为在服务器端它仍然是打开的 - 并写入消息。同时,服务器已经成功关闭了 f。接下来,在客户端执行的刷新会在管道上执行 write() 系统调用,这会触发 SIGPIPE,因为在另一侧它现在已经关闭了。
如果我没错的话,你应该能够通过将服务器的 f.close() 移动到 g.write(...) 上面来解决这个问题。

这就是断开管道的问题所在!这也是我对FIFO不理解的地方...我以为在客户端进行open/write/close序列后重新打开管道,如果服务器只进行了open/read而没有进行close(因此基本上服务器仍处于第一次打开的“会话”中),那么它将会停滞。这种行为使得使用两个FIFO进行双向通信变得更加困难。 - 6502
@6502:是的。实际上,UNIX 域套接字通常更适合本地进程之间的通信。 - caf

0

我不是Unix专家,但我的猜测是你最终会在两个进程中关闭文件,然后进行写入操作。由于没有接收数据的内容,管道就会断开。

我不明白为什么你一直在打开和关闭管道。

尝试先启动读取管道的进程,让它打开管道并等待数据。

然后启动管道写入程序,并将要发送的所有数据都传输出去。如果它超前了,它将停止。当写入程序关闭管道时,读取程序将得到零字节而不是阻塞,并应该关闭。我IRC,Python会检测到这一点并返回EOF。


使用FIFO打开写入时,如果没有人读取,它不会中断...它只是等待。实际上,我可以先运行服务器或客户端,对于多个请求,一切都可以正常工作(我已经看到从几百个到60000+的请求)。然后,由于我不理解的原因,在客户端的刷新命令上出现了一个破损的管道。 - 6502
不要关闭文件。当服务器正在向标准输出写入时,文件f被关闭,因此客户端的刷新操作将失败。 - Ian
服务器在写完输出后才关闭输入,因此客户端必须已经通过flush调用并打开输出FIFO以进行读取。我有什么遗漏吗? - 6502

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接