簡體   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