2016-04-23 25 views
3

我怎麼能中斷在Python 3.X阻塞Queue.get()中斷一個Queue.get

在Python 2.X setting a long timeout似乎工作,但同樣不能爲Python 3.5可說的。

運行在Windows 7上,CPython的3.5.1,64位機都和Python。 似乎它在Ubuntu上表現不一樣。

+0

不知道,這正是爲什麼我問的問題 – Bharel

+0

其工作原理是在Python 3比如:'python3 -c「導入隊列; queue.Queue( ).get()「'在我的Ubuntu機器上由'Ctrl + C'成功中斷。你確定你使用python3可執行文件,而不是python2嗎? – jfs

+0

在我的窗口上Ctrl + C不會中斷 – Mixone

回答

5

它在Python 2上工作的原因是,在Python 2上超時的Queue.get執行得非常糟糕,as a polling loop with increasing sleeps between non-blocking attempts to acquire the underlying lock; Python 2實際上並不具有支持定時阻塞獲取的鎖定原語(這是變量需要的內部變量,但缺少,因此它使用繁忙循環)。當你想這對Python 2中,你檢查所有是Ctrl-C是否的(短)一個後處理time.sleep通話結束,並the longest sleep in Condition is only 0.05 seconds,這是如此之短,你可能就算你按Ctrl不會注意到-C即刻開始新的睡眠。

的Python 3具有真定時鎖獲取支持(由於縮小目標的操作系統的數目的那些配有本地定時互斥或某種旗語)。因此,您實際上在整個超時期間阻止了鎖的獲取,在輪詢嘗試之間的時間不會阻塞0.05s。

它看起來像Windows允許用於註冊處理程序按Ctrl-C是不是意味着Ctrl-C doesn't necessarily generate a true signal,所以獲取鎖不被中斷來處理它。當定時鎖獲取最終失敗時,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被按下,它將在此時上升t OO)
+0

兩個注意事項 - 首先'queue.get()'調用的一秒超時會給用戶帶來明顯的延遲,我推薦更短一些。其次,保持'stoptime'和'remaining'違反[DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) - 只需使用'while time.monotonic()

+0

@SteveCohen:我已經調整以消除附加變量(並減少每循環算術);我不能直接使用'time.monotonic() ShadowRanger

+0

當然,如果timeout超過1,'queue.get()'不會被執行。這真是太糟糕了,因爲'queue.get()'應該至少發生一次。如果使用更短​​的超時時間,則可以忽略(微不足道的)超限情況並簡化整個shebang。正如你在迴應中提到的那樣,Python 2在0.05秒(ish)輪詢。 –

1

正如評論跟帖中提到上面提供的巨大答案@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=min(1, timeout - time.monotonic())) 
     except queue.Empty: 
      if time.monotonic() > timeout: 
       raise 
      else: 
       pass 
+1

雖然我真的很喜歡你的答案,但是請記住,隨着時間的推移,你會失去這裏的準確性,你會睡1秒,秒數,並捕獲異常也需要時間,你不考慮它。在@ ShadowRanger的答案,超時5.1234秒將是5.1234 +〜0.00002,在你的情況下,它甚至可以達到5.1236。可能無關緊要在平常的情況下,但誰知道。 – Bharel

+0

@Bharel:感謝您的反饋。我認爲這個問題,我同意 - 這是很遠的不太精確。但是,對於小型超時,差異將可以忽略不計。如果需要最終的精度(並且我渴望聽到它爲什麼會這樣),那麼可以在保持單個'return'/'queue.get()'結構的同時加入對'time.monotonic()'的調用。我已經添加,作爲一個編輯。 –

+0

您無需爲了找到適用的情況而走向極端。假設您有一臺服務器運行數天,如果沒有答案,3天后您希望關閉服務器。您可以在3天內獲得當前時間和凌晨12:00之間的時差。在他的情況下,你會在隊列中睡3天(+ 0.00001秒),誰知道。在這種情況下,在隊列上睡覺實際上是實現它的好方法。你的設計比較乾淨,但我更喜歡它。只需將其改爲單調,它很棒:-) – Bharel

相關問題