![](/img/trans.png)
[英]After interrupting Queue.get(timeout=60), the following Queue.get() does not return after another thread has succesfully Queue.put('something'). Why?
[英]Interrupting a Queue.get
如何在 Python 3.X 中中断阻塞的Queue.get()
?
在 Python 2.X中,长时间的超时设置似乎有效,但对于 Python 3.5 则不能这样说。
在 Windows 7、CPython 3.5.1、64 位机器和 Python 上运行。 似乎它在 Ubuntu 上的表现不同。
它在 Python 2 上工作的原因是在 Python 2 上超时的Queue.get
实现得非常糟糕,作为轮询循环,在非阻塞尝试获取底层锁之间增加睡眠; Python 2 实际上并没有支持定时阻塞获取的锁原语(这是Queue
内部Condition
变量所需要的,但缺乏,因此它使用了忙循环)。 当您在 Python 2 上尝试此操作时,您要检查的只是在(短) time.sleep
调用之一完成后是否处理了Ctrl-C
,并且Condition
最长的 sleep 仅为 0.05 seconds ,就是这样简而言之,即使您在新睡眠开始的那一刻按下 Ctrl-C,您也可能不会注意到。
Python 3 具有真正的定时锁定获取支持(由于将目标操作系统的数量缩小到具有本机定时互斥锁或某种信号量的那些操作系统)。 因此,您实际上在整个超时期间阻塞了锁定获取,而不是在轮询尝试之间一次阻塞 0.05 秒。
看起来 Windows 允许为 Ctrl-C 注册处理程序,这意味着Ctrl-C
不一定会生成真正的信号,因此不会中断锁定获取来处理它。 当定时锁获取最终失败时,Python 会收到Ctrl-C
通知,因此如果超时时间很短,您最终会看到KeyboardInterrupt
,但直到超时过去才会看到它。 由于 Python 2 Condition
一次(或更少)只休眠 0.05 秒,因此 Ctrl-C 总是被快速处理,但 Python 3 将休眠直到获得锁。
Ctrl-Break
保证表现为信号,但它也不能被 Python 正确处理(它只是终止进程),这可能也不是你想要的。
如果你想让Ctrl-C
工作,你在某种程度上会被困在轮询中,但至少(与 Python 2 不同)你可以有效地轮询Ctrl-C
同时在其余时间在队列中实时阻塞(所以你是提醒项目立即免费,这是常见的情况)。
import time
import queue
def get_timed_interruptable(q, timeout):
stoploop = time.monotonic() + timeout - 1
while time.monotonic() < stoploop:
try:
return q.get(timeout=1) # Allow check for Ctrl-C every second
except queue.Empty:
pass
# Final wait for last fraction of a second
return q.get(timeout=max(0, stoploop + 1 - time.monotonic()))
这一次阻塞一秒钟,直到:
Empty
正常传播)Ctrl-C
(在该秒的剩余时间过后, KeyboardInterrupt
被引发)Ctrl-C
,它也会在此时升起)正如上面提供的对@ShadowRanger 的伟大答案的评论线程中所述,这是他的功能的另一种简化形式:
import queue
def get_timed_interruptable(in_queue, timeout):
'''
Perform a queue.get() with a short timeout to avoid
blocking SIGINT on Windows.
'''
while True:
try:
# Allow check for Ctrl-C every second
return in_queue.get(timeout=min(1, timeout))
except queue.Empty:
if timeout < 1:
raise
else:
timeout -= 1
正如@Bharel 在评论中指出的那样,这可能比绝对超时时间长几毫秒,这可能是不可取的。 因此,这是一个精度明显更高的版本:
import time
import queue
def get_timed_interruptable_precise(in_queue, timeout):
'''
Perform a queue.get() with a short timeout to avoid
blocking SIGINT on Windows. Track the time closely
for high precision on the timeout.
'''
timeout += time.monotonic()
while True:
try:
# Allow check for Ctrl-C every second
return in_queue.get(timeout=max(0, min(1, timeout - time.monotonic())))
except queue.Empty:
if time.monotonic() > timeout:
raise
只需使用不会阻塞的get_nowait
。
import time
...
while True:
if not q.empty():
q.get_nowait()
break
time.sleep(1) # optional timeout
这显然是在忙着等待,但q.get()
基本上做了同样的事情。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.