2011-11-18 80 views
1

似乎多線程程序中的異步信號沒有被Python正確處理。但是,我想我會在這裏檢查是否有人能夠發現我違反某個原則的地方,或者誤解了某些概念。異步鍵盤中斷和多線程

也有類似的線程,我在這裏找到了SO,但沒有一個看起來完全一樣。

該場景是:我有兩個線程,讀者線程和寫入器線程(主線程)。寫入器線程寫入讀取器線程輪詢的管道。這兩個線程使用基元(我假設使用pthread_cond_wait執行)進行協調。主線程在Event上等待,而讀取器線程最終設置它。

但是,如果我想在主線程正在等待Event時中斷我的程序,則不會異步處理KeyboardInterrupt。

這裏是一個小程序來說明我的觀點:

#!/usr/bin/python 
import os 
import sys 
import select 
import time 
import threading 

pfd_r = -1 
pfd_w = -1 
reader_ready = threading.Event() 

class Reader(threading.Thread): 
    """Read data from pipe and echo to stdout.""" 
    def run(self): 
     global pfd_r 
     while True: 
      if select.select([pfd_r], [], [], 1)[0] == [pfd_r]: 
       output = os.read(pfd_r, 1000) 
       sys.stdout.write("R> '%s'\n" % output) 
       sys.stdout.flush() 
       # Suppose there is some long-running processing happening: 
       time.sleep(10) 
       reader_ready.set() 


# Set up pipe. 
(pfd_r, pfd_w) = os.pipe() 
rt = Reader() 
rt.daemon = True 
rt.start() 

while True: 
    reader_ready.clear() 
    user_input = raw_input("> ").strip() 
    written = os.write(pfd_w, user_input) 
    assert written == len(user_input) 
    # Wait for reply -- Try to ^C here and it won't work immediately. 
    reader_ready.wait() 

開始與「./bug.py」程序,在提示符下輸入一些。一旦看到閱讀器以前綴'R>'回覆,請嘗試使用^C中斷。

我所看到的(Ubuntu Linux 10.10,Python 2.6.6)是^C直到阻塞reader_ready.wait()返回後才被處理。我期望看到的是^C異步引發,導致程序終止(因爲我沒有捕獲KeyboardInterrupt)。

這可能看起來像一個人爲的例子,但我在真實世界的程序中遇到了這個問題,time.sleep(10)被實際計算所取代。

我在做什麼明顯錯誤,就像誤解預期的結果會是什麼?

編輯:我也剛剛用Python 3.1.1進行了測試,存在同樣的問題。

回答

1

threading._Event對象的wait()方法實際上依賴於thread.lockacquire()方法。但是,thread documentation指出鎖的acquire()方法不能被中斷,並且任何KeyboardInterrupt異常將在鎖釋放後處理。

所以基本上,這是按預期工作的。實現此行爲的線程對象依賴於某個點(包括隊列)的鎖定,因此您可能需要選擇另一個路徑。

+0

太好了,謝謝你的解釋。我可能會說這個特殊的「功能」實際上是一個錯誤....我想我必須找出另一種方法來同步我的線程。這似乎很愚蠢,我不能使用內置的同步原語來做到這一點。 –

+0

好吧,你可以去做一個漂亮的hackish:'while 1:'(Line break)'如果reader_ready.wait(1):break' –

+0

我可以這樣做,或者使用'reader_ready.wait(99999)'作爲超時時間。它似乎偶然會做一個「睡眠」,所以鍵盤中斷可以被處理。儘管如此,一個黑客。 –

0

或者,您也可以使用signal模塊的pause()功能代替reader_ready.wait()signal.pause()是一個阻塞函數,當進程收到一個信號時會被解除阻塞。在你的情況下,當按下^C時,SIGINT信號解除該功能。

根據文檔,該功能不適用於Windows。我已經在Linux上測試過它,它工作。我認爲這比使用wait()超時更好。