select() 之后的读取 - 在管道上阻塞

0 投票
2 回答
1053 浏览
提问于 2025-04-17 20:03

我的Python程序需要同时处理来自几个不同文件描述符的读取。有些是子进程的标准输出和错误输出描述符,其他的是与inotify调用相关的文件描述符。

我遇到的问题是,在调用select()之后,如何进行“非阻塞”的读取。根据文档select()报告可以写入的套接字“保证在写入最多PIPE_BUF字节时不会阻塞”。

我想,读取时没有这样的保证是合理的,因为select()报告内核管道缓冲区中有数据准备好,并不意味着你可以直接调用.read(socket.PIPE_BUF),因为里面可能只有几字节的数据。

这就意味着,当我在套接字上调用read()时,可能会出现死锁的情况,因为一些子进程很少产生输出。

有没有什么办法可以解决这个问题?我现在的解决方法是调用readline(),而且我很幸运,所有我读取的内容都是逐行输出。在这种情况下,select()还有什么用吗?因为我们无法知道可以安全读取多少字节而不阻塞。

[1] 我知道这与O_NONBLOCK套接字是不同的。

2 个回答

0

作为一个替代方案,我遇到了完全相同的问题,并通过使用 readline(1) 来解决这个问题。我把读取到的内容添加到一个内部缓冲区,直到 readline 返回我想要处理的字符(比如换行符、空格等)。

更详细一点:我在一个文件描述符上调用了 select(),然后对 select 返回的每个文件描述符都调用 readline(1),把读取到的字符添加到缓冲区中,重复这个过程,直到 readline 返回我想要的内容。然后我返回我的缓冲区,清空它,继续进行其他操作。顺便提一下,我还返回了一个布尔值,让调用这个方法的人知道我返回的数据是否因为读取错误而为空,还是因为还没有读取完。

我还实现了一个版本,可以在超时的情况下进行处理。如果我在缓冲区中等待了 x 毫秒而没有找到换行符或文件结束符(EOF),那么就返回缓冲区中的内容。

我现在正在尝试找出是否有办法询问一个文件描述符有多少字节等待被读取,然后直接 readline([那么多字节])...

希望这些对你有帮助。

3

你可以继续去read每一个管道和套接字:这样你就能获取到现在可用的任何数据:

>>> import os
>>> desc = os.pipe()
>>> desc
(3, 4)
>>> os.write(desc[1], 'foo')
3
>>> os.read(desc[0], 100)
'foo'
>>> os.read(desc[0], 100)

[在这里会卡住,因为没有可用的输入,可以用^C来中断]

...
KeyboardInterrupt
>>> os.write(desc[1], 'a')
1
>>> os.read(desc[0], 100)
'a'
>>> 

撰写回答