Linux 下 Python 读取命名管道的疑惑

2015-08-04 18:33:25 +08:00
 hosiet

先说需求:

文件系统里已经有一个命名管道(FIFO),偶尔有其它程序对其进行写入,需要将这些写入由 Python 程序进行读取,一次读一行内容。

我的最初想法是程序不断地读取FIFO(f.readline()),由于命名管道是阻塞的,读完之后再继续读取应该会阻塞,这样其它程序继续写入一行的时候 Python 程序会立刻读到数据。

所以写了一个小脚本做实验:

FILEPATH = '/tmp/myfifo'

f = open(FILEPATH, 'r')
while True:
    line = f.readline()
    print(line)

但是效果是程序首先在 open() 处阻塞(这是 fifo 的特性,可以理解)。之后如果使用 echo 向 fifo 写入数据:

echo "abc" > /tmp/myfifo

那么程序会立刻打印出 'abc',然后被空字符串刷屏。如何解决 f.readline() 没有阻塞反而返回了一个空字符串的问题呢?

PS:Google 上搜索到的全都是使用非阻塞的方法,看了一圈也没有找到解决方法。使用 os.read() 或者 os.open() 试了试貌似也没有效果。

平台是 Linux 3.19 / Python 3.4.3

5822 次点击
所在节点    Python
12 条回复
yuelang85
2015-08-04 20:39:12 +08:00
不是太理解你的问题。

首先,堵塞了,只不过运算速度太快,所以看起来没有堵塞。

while True这里就是不停的读取这个文件啊,并且把读取到的行打印出来,readline刚好是一行一行读下去的,你没有新的行输入,所以返回了“空”,所以就不停刷屏了。

你判断下line是否为空,非空再打印就好了
yuelang85
2015-08-04 20:41:24 +08:00
另外,如果你把open放在while True外面,是看不到文件变化的
pubby
2015-08-04 20:43:33 +08:00
后面刷屏的应该是readline失败了吧,因为echo结束 myfifo写入端关闭了
pubby
2015-08-04 20:44:47 +08:00
open外面再套一层循环, readline后检测eof之类的判断
hosiet
2015-08-04 20:48:52 +08:00
@yuelang85 实际上想实现的目的是读取到末尾管道中没有数据时,readline 函数因为没有输入又没有遇到 EOF 而阻塞,直到管道中又有数据输入,有换行符为止而使 readline 函数返回该行数据。就像是 shell 中

cat /tmp/myfifo

时阻塞住的情况,而不要在没数据时返回空字符串。

open() 放在循环外面是因为打开文件只需要一次,本来就不需要多次调用 open()。
hosiet
2015-08-04 20:50:43 +08:00
@pubby 不是很想通过轮询的方式判断有没有数据,占用 CPU 时间有点多
akira
2015-08-04 20:53:31 +08:00
@hosiet 所以你需要的是非阻塞的模式
pubby
2015-08-04 20:55:48 +08:00
@hosiet 我的意思是 echo "abc" >> /tmp/myfifo 干了3件事情
1. w方式打开myfifo
2. 写入abc\n
3. close myfifo

因此 f.readline()后肯定遇到eof了

你可以试验一下的

$ mkfifo myfifo
$ cat myfifo

另外开个shell
$ echo "abc" >> myfifo

可以看到第一个shell中 cat结束了。
hosiet
2015-08-05 03:14:43 +08:00
@pubby 感谢,这个解释可能说明了一些问题。

按我理解,现在这种应用应当存在两种情况:

第一种:Python 程序打开 FIFO 读,有另外的程序打开 FIFO 写,并且没有写完,此时读取文件在默认(阻塞)条件下会阻塞。但是如果以非阻塞模式打开文件读会怎么样呢?(待实验,是否出现 EAGAIN/EWOULDBLOCK?)

第二种:Python 程序打开 FIFO 读,已经有程序打开过 FIFO 写并关闭了文件,此时一直读取文件一定会碰到 EOF,之后再以任何方式读取 FIFO 就像是读取普通文件遇到文件末尾后继续读返回空字符串那样返回了空字符串而非阻塞。是这样吗?如果以非阻塞方式打开文件读又会如何?(待实验)

@akira 使用非阻塞方式,是在 Python 程序中以非阻塞方式打开文件读吗?我觉得并不符合情景要求,因为任何情况下 read 都会立刻返回。
pubby
2015-08-05 03:41:10 +08:00
如果没有读,你根本没机会去写入的
hosiet
2015-08-05 08:26:13 +08:00
@pubby 的确是这样的,如果没有一个以读模式打开FIFO文件的进程,那么其它进程以写模式打开文件会卡在 open() 中。但是在现在的情况下始终有一个以读模式打开文件的进程,所以并不存在问题。

我只是想要一个符合情景的解决方案 =_=
hosiet
2015-08-05 09:20:38 +08:00
好了,找到了一个 dirty hack:

import os, time
import multiprocessing

FM_FILEPATH = '/tmp/myfifo'

def mf_keeper(filepath=FM_FILEPATH):
"""
Open it. Don't close it.
"""
f2 = open(filepath, 'w')
while True:
time.sleep(60)
pass

if __name__ == "__main__":
p = multiprocessing.Process(target=mf_keeper)
p.start() # never join
f = open(FM_FILEPATH, 'r')
while True:
line = f.readline()
print("I have \"{}\"".format(line))

似乎 f.readline() 读取时候只会对第一个打开FIFO的进程所写入的 EOF 反应。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/210773

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX