为什么Python的queue.Queue.get()允许在超时前提前返回?

4 投票
3 回答
2422 浏览
提问于 2025-04-16 19:05

更新:这个问题是因为我对Queue.get()的行为理解错了,主要是因为文档有点模糊,还有我自己写的timedelta.total_seconds()函数有bug。我在试图证明原来的答案不对的时候发现了这个bug。现在Python已经提供了timedelta.total_seconds()(从2.7版本开始),我会改用这个。

抱歉让大家困惑了。


这个问题不是“为什么我的代码不运行?”而是“这个设计决定背后的动机是什么?”

从2.3版本开始,Python的队列模块里有一个Queue类,它有一个get方法,可以接受一个超时参数。手册里是这么写的:

Queue.get([block[, timeout]])
从队列中移除并返回一个项目。如果可选参数block为真且timeout为None(默认值),那么如果没有项目可用就会一直等待。如果timeout是一个正数,它最多会等待timeout秒,如果在这段时间内没有项目可用,就会抛出Empty异常。 [...]

(强调是我加的)

注意,即使没有达到超时,它也可能会抛出Empty异常。实际上,我在Ubuntu上看到了这种情况(但在Windows上没有)。它提前退出了一点,这对我的代码有些小影响,不过我可以绕过这个问题。

大多数阻塞超时设置的是最小超时,这在非实时操作系统上是有道理的,包括Windows和Linux。因为没有保证操作系统会在任何给定的截止时间内切换到你的进程或线程。

然而,这个方法设置的是最大超时。有没有人能解释一下这个设计决定为什么会有意义?

3 个回答

0

Queue.get这个功能里,肯定有个问题,至少在python 2.6.6版本中是这样的。在posix系统上,当我用queue.get(timeout=1)时,它几乎是立刻就退出了,并且会抛出一个叫做Empty的异常,而queue.get(timeout=2)则正常工作。

我当时是在用一个队列,多个线程同时从这个队列里**数据……

5

除非你在做实时编程,否则一个正确的程序应该把任何超时时间当作参考值来看待,所以出错在哪一边其实没那么重要。需要注意的是,queue 确实会等待至少超时时间的长度,如果队列一直是空的(这一点可以通过查看代码确认,代码基本上是一个循环,当 remaining(剩余时间)小于0时就会退出)。不过,如果在这段时间内有东西被放入队列,它会更早返回。

你期待的行为是:

[0] (Thread) A calls Queue.get(True, 10)
[0] B waits 9 seconds
[9] B does something on X
[11] A destroys X

但一个有效的调度也可以是:

[0] (Thread) A calls Queue.get(True, 10)
[0] B waits 9 seconds
[9] Operating system is busy for 2 seconds
[11] A gets picked by the OS, destroys X    
[12] B does something on X

你需要修正程序,使其在选择线程时不依赖于实际的时间。

6

我觉得你可能误解了文档的意思。文档并不是说它可能在不到 timeout 秒的时间内抛出 Empty 异常,而是说它最多会阻塞 timeout 秒。如果可以满足 get 的请求,它可能会在更短的时间内解除阻塞。

我明白你说你看到它早早就抛出了 Empty,但老实说,这听起来要么是个bug,要么就是你对系统的准确性要求得太高了。(看起来,为了遵循规范的确切表述,实际的实现应该把 timeout 向下取整到它的计时器的精度,而不是像你希望的那样向上取整。)

撰写回答