在多线程Python程序中,一个线程有时会使用内置的raw_input()请求控制台输入。我希望能够在raw_input提示符下关闭程序,方法是在shell中键入^C(即使用SIGINT信号)。但是,当子线程执行raw_input时,键入^C什么也不做——KeyboardInterrupt直到我返回(留下raw_input)才会引发。
例如,在以下程序中:
import threading
class T(threading.Thread):
def run(self):
x = raw_input()
print x
if __name__ == '__main__':
t = T()
t.start()
t.join()
在输入完成之前,键入^C不会有任何作用。然而,如果我们只调用T().run()
(即单线程情况:只在主线程中运行raw_input),^C会立即关闭程序。
据推测,这是因为SIGINT被发送到主线程,当控制台上的分叉线程块读取时,主线程被挂起(等待GIL)。主线程直到在raw_input返回后获取GIL后才能执行其信号处理程序。(如果我错了,请纠正我——我不是Python线程实现方面的专家。)
有没有一种方法可以像raw_input一样从stdin读取,同时允许主线程处理SIGINT,从而降低整个过程?
[我在MacOSX和一些不同的Linux上观察到了上面的行为。]
编辑:我错误地描述了上面的根本问题。在进一步的研究中,正是主线程对join()
的调用阻止了信号处理:Guido van Rossum自己解释说,联接中的底层锁获取是不间断的。这意味着信号实际上被推迟到整个线程完成——所以这实际上与raw_input
无关(只是后台线程正在阻塞,因此连接没有完成)。
当在没有超时的情况下调用join时,它是不可中断的,但当在有超时的情况时调用它,它是可中断的。尝试添加任意超时并将其放入while循环:
while my_thread.isAlive():
my_thread.join(5.0)
真的没有简单的方法来解决这个问题。
一种方法是重新组织和分解代码,使需要Ctrl-C可中断性的部分函数在主线程上执行。您可以使用队列来发送执行请求,同样也可以使用队列发送结果值。您需要一个主线程的输入队列,每个非主线程需要一个输出队列;以及协调的主线程出口。显然,以这种方式在任何给定时间只执行一个阻塞函数,这可能不是您想要的。
下面是这个想法的一个工作示例,其中对协调的主线程出口使用了稍微有点反常的信号量。