繁体   English   中英

中断队列.get

[英]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()))                

这一次阻塞一秒钟,直到:

  1. 剩余时间小于一秒(它阻塞剩余时间,然后允许Empty正常传播)
  2. 在一秒间隔内按下Ctrl-C (在该秒的剩余时间过后, KeyboardInterrupt被引发)
  3. 获得一个项目(如果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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM