![](/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.