python - select.select() 是如何工作的?

31

背景:

我熟悉C语言的select()函数,已经在许多场合使用过它。其中大部分是用于读写管道、文件等操作。我必须说,我从未使用过错误列表,但这与关键问题无关。

问题:

Python的select()是否表现如下?

对我而言,尽管Python的select()接口非常直观,但它似乎以不同的方式工作。看起来select()会在第一次准备好读取文件时返回。如果你在留下一些字节需要读取的情况下读取文件,调用select()将会阻塞。但如果在两次调用select()之间没有进行任何读取调用,再次调用select()将会按预期返回。例如:

import select
# Open the file (yes, playing around with joysticks)
file = open('/dev/input/js0', 'r') 
# Hold on the select() function waiting
select.select([file], [], [])
# Say 16 bytes are sent to the file, select() will return.
([<open file '/dev/input/js0', mode 'r' at 0x7ff2949c96f0>], [], [])
# Call select() again, and select() will indeed return.
select.select([file], [], [])
([<open file '/dev/input/js0', mode 'r' at 0x7ff2949c96f0>], [], [])
# read 8 bytes. There are 8 bytes left for sure. Calling again file.read(8) will empty the queue and would be pointless for this example
file.read(8)
'<\t\x06\x01\x00\x00\x81\x01'
# call select() again, and select() will block
select.select([file], [], [])
# Should it block? there are 8 bytes on the file to be read.

如果这是Python中select()的行为,那我可以接受并处理它。虽然不是我预期的,但没关系,我知道该怎么做。
但如果这不是select()的行为,我希望有人告诉我我做错了什么。我所读到的关于select()的内容就是Python文档中所说的:"select()返回任何在读取|写入|错误列表中的文件是否准备好进行读取|写入|错误检查。" 没有谎言。也许问题应该是:
  • 在Python中什么情况下认为一个文件已经准备好进行读取?
  • 这意味着从未读取过的文件吗?
  • 这意味着有待读取字节的文件吗?
2个回答

22

select()函数在Python中会被转化成系统调用select(),但阻塞的问题可能与缓冲有关。建议用文件系统而非特殊设备(如摇杆)进行读写操作来验证 select() 是否正常工作。

你需要改变open()的调用方式。默认情况下,Python的 open 函数会使用缓冲读取,因此即使你只读取了8个字节,它也可能会读取更多数据并将结果缓存起来。你需要在打开摇杆设备时将openbuffering 选项设置为无缓存。

建议尝试以下操作:

  • Python默认以文本模式打开文件。当处理特殊设备(例如摇杆)时,应该将打开模式设置为rb
  • 以无缓存模式打开文件。
  • 如果要使用基于select的调用,请将设备设置为非阻塞模式。尝试使用带有os.O_RDONLY | os.O_NONBLOCK标志的os.open()函数。

1
使用0缓冲区大小调用open()就可以了,这对我来说很有意义。没有必要将文件描述符设置为非阻塞,也不需要更改读取模式。这样更好,因为在读取后需要将字符串unpack()成结构体。 - Sebastian

0

我可以问一个愚蠢的问题吗 - 你确定还剩下8个字节吗?

设备节点不一定像普通文件那样行为。可能需要在单个read()系统调用中读取整个struct input_event结构体。(如果你没有读取足够的数据,其余部分将被丢弃)。有点像数据报套接字上的recvmsg()。


@user799204,这是一个愚蠢的问题吗?你读过陈述吗?而且,是的,我确定那个作为例子发布的事实。如果你想知道,唯一的方法就是自己尝试。遵循的步骤是插入一个操纵杆,同时按下两个按钮。这会生成一个与select()同时发生的事件。发出file.read(8)并再次调用select()以查看我提出这个问题的原因。 - Sebastian
哦,顺便说一下,8 是特定事件的字节数。但是如果你愿意,你可以使用比队列中总字节数少的任何数量。请参见被接受的答案,缓冲是问题所在。 - Sebastian
抱歉我的措辞不够清晰。我是指我的问题可能很愚蠢。(这也表现出来了。struct input_event仅适用于“event”文件。您的8个字节是struct js_event的正确大小)。 - sourcejedi
对我来说没问题,编辑也很好。只是提一下你所说的UDP数据包:你是在谈论数据包丢失吗?UDP本来就是这样设计的,它没有控制传输。我不认为有人会编写一个驱动程序,在非确定性的情况下“可能”缺少数据。或者,你是在谈论队列满的问题。如果您的设备队列已满,则旧事件将被新事件替换,因此,是的,您可能会以这种方式“丢失”数据。无论如何,这不是问题所在。问候。 - Sebastian
我在想你的读取大小是否太小了。如果你从UDP中天真地读取8个字节,而数据包是16个字节,那么最后8个字节就会丢失(这反映在MSG_TRUNC标志中)。 - sourcejedi

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