在子线程中用^C/KeyboardInterrupt中断Python的raw_input()

8 投票
2 回答
2736 浏览
提问于 2025-04-17 12:59

在一个多线程的Python程序中,有一个线程有时会使用内置的raw_input()来请求控制台输入。我希望在raw_input提示时,可以通过在终端输入^C来关闭程序(也就是发送一个SIGINT信号)。但是,当子线程正在执行raw_input时,输入^C没有任何反应——直到我按下回车键(退出raw_input),KeyboardInterrupt才会被触发。

例如,在下面的程序中:

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返回后才能执行它的信号处理程序。(如果我说错了,请纠正我——我对Python的线程实现不是很专业。)

有没有办法以类似raw_input的方式从标准输入读取,同时让主线程处理SIGINT,从而关闭整个进程呢?

[我在Mac OS X和几个不同的Linux上观察到了上述行为。]


编辑:我之前对底层问题的描述不太准确。经过进一步调查,发现是主线程调用join()导致信号处理被阻止:Guido van Rossum本人解释过join中的底层锁获取是不可中断的。这意味着信号实际上会被延迟,直到整个线程完成——所以这实际上与raw_input没有关系(只是因为后台线程被阻塞,导致join无法完成)。

2 个回答

1

这件事没有简单的解决办法,真的。

一种方法是重新组织和拆分你的代码,让需要Ctrl-C中断的部分在主线程上执行。你可以使用队列来发送执行请求和结果值。你需要一个输入队列给主线程,还有一个输出队列给每个非主线程;同时要协调好主线程的退出。显然,这样做的话,每次只能执行一个阻塞的函数,这可能不是你想要的效果。

这里有一个这个想法的工作示例,里面稍微复杂地使用了信号量来协调主线程的退出。

6

当你调用 join 方法时,如果没有设置超时时间,它就无法被打断;但如果设置了超时时间,它就可以被打断。你可以试着设置一个随便的超时时间,然后把它放在一个循环里:

while my_thread.isAlive():
    my_thread.join(5.0)

撰写回答