Python读取命名管道

33

我在Linux上有一个命名管道,想要用Python从中读取。问题是Python进程会连续占用一个核心(100%),导致系统负载过高。以下是我的代码:

FIFO = '/var/run/mypipe'
os.mkfifo(FIFO)
with open(FIFO) as fifo:
    while True:
        line = fifo.read()
我想问一下,使用“睡眠”是否会有助于情况,或者过程会丢失来自管道的一些输入数据。我无法控制输入,因此我不知道数据输入的频率。我阅读了关于select和poll的资料,但是没有找到适用于我的问题的示例。最后,我想问一下100%的使用率是否会对数据输入产生任何影响(丢失或其他什么)?
编辑:我不想中断循环。我希望进程持续运行并从管道“接收”数据。

在循环中,print(line) 输出什么?另外,你是如何写入它的? - Padraic Cunningham
脚本等待来自另一个进程的数据。如果它接收到一个特定字符串,它会执行一些API调用。 - user1005633
那么当你打印(line)时,循环中不断看到新数据了吗? - Padraic Cunningham
2个回答

53

按照典型的Unix风格,read(2)返回0个字节来表示文件结束,这可能意味着:

  • 文件中没有更多的字节
  • 套接字的另一端关闭了连接
  • 写入者已经关闭了管道

在您的情况下,fifo.read()返回一个空字符串,因为写入者已经关闭了它的文件描述符。

您应该检测到这种情况并跳出循环:

reader.py:

import os
import errno

FIFO = 'mypipe'

try:
    os.mkfifo(FIFO)
except OSError as oe: 
    if oe.errno != errno.EEXIST:
        raise

print("Opening FIFO...")
with open(FIFO) as fifo:
    print("FIFO opened")
    while True:
        data = fifo.read()
        if len(data) == 0:
            print("Writer closed")
            break
        print('Read: "{0}"'.format(data))

示例会话


终端1

$ python reader.py 
Opening FIFO...
<blocks>

终端 2:

$ echo -n 'hello' > mypipe 

终端1:

FIFO opened
Read: "hello"
Writer closed
$ 

更新1 - 持续重新打开

您表示希望保持对管道的写入监听,即使写入端已关闭。

为了有效地实现这一点,您可以(并且应该)利用以下事实:

通常情况下,打开FIFO会阻塞,直到另一端也被打开。

在此,我在openread循环周围添加了另一个循环。这样,一旦管道关闭,代码将尝试重新打开它,这将阻塞直到另一个写入器打开管道:

import os
import errno

FIFO = 'mypipe'

try:
    os.mkfifo(FIFO)
except OSError as oe:
    if oe.errno != errno.EEXIST:
        raise

while True:
    print("Opening FIFO...")
    with open(FIFO) as fifo:
        print("FIFO opened")
        while True:
            data = fifo.read()
            if len(data) == 0:
                print("Writer closed")
                break
            print('Read: "{0}"'.format(data))

终端 1:

$ python reader.py 
Opening FIFO...
<blocks>

终端 2:

$ echo -n 'hello' > mypipe 

终端 1:

FIFO opened
Read: "hello"
Writer closed
Opening FIFO...
<blocks>

终端 2:

$ echo -n 'hello' > mypipe 

终端1:

FIFO opened
Read: "hello"
Writer closed
Opening FIFO...
<blocks>

您可以通过阅读管道的man页面了解更多信息:


我不想打破循环。我想要持续从中读取。脚本等待来自另一个进程的数据。如果它获取到一个包含特定字符串的字符串,它会执行一些API调用... - user1005633
1
“我想要持续从中读取。” “不,你不需要这样做。这会导致您的 CPU 利用率达到 100%。正如您在我的更新示例中所看到的那样,open 将阻塞,直到有一个写入者。您需要做的是关闭管道,然后允许 open 阻塞,直到您重新打开它。请参考我的第一次更新。” - Jonathon Reinhart
我强烈建议您完全阅读我链接的手册页面,并完全理解管道/命名管道的打开、读取和写入语义。 - Jonathon Reinhart
使用 for line in fifo 而不是 fifo.read() 似乎也有帮助。 - huggie
1
@huggie 如果您的管道是以换行符结尾的字符串,则可以这样做。 - Jonathon Reinhart

13

数年后,如果我正确理解了 OP 的用例,使用for ... in ...正好可以达到预期的效果:

import os

FIFO = 'myfifo'
os.mkfifo(FIFO)
with open(FIFO) as fifo:
    for line in fifo:
        print(line)

这个程序会耐心地等待来自FIFO的输入,直到有输入后才将其打印在屏幕上。同时,在此期间不使用CPU资源。

这也是Python中更惯用的方法,因此建议使用它,而不是直接使用read()。

如果客户端向FIFO写入结束,则for循环结束,程序退出。如果您希望重新打开FIFO以等待下一个客户端打开它,可以将for部分放入while循环中:

import os

FIFO = 'myfifo'
os.mkfifo(FIFO)
while True:
    with open(FIFO) as fifo:
        for line in fifo:
            print(line)

这将重新打开FIFO并像往常一样等待。


2
(数年后)根据已采纳答案的评论所述,只有当“fifo”包含以换行符分隔的字符串时,“for line in fifo”才起作用。 - clemisch
是的,太烦人了!因为这个原因,我开始重新考虑使用FIFO进行进程间通信。有一些更加强大的方法,比如multiprocessing.Queue https://docs.python.org/2/library/multiprocessing.html#exchanging-objects-between-processes - Tristan

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