2011-11-18 47 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()超时更好。