为什么我的Python线程/多进程脚本无法正常退出?
我有一个服务器脚本,需要能够正常关闭。在测试常用的 try..except
语句时,我发现 Ctrl-C
的效果和我预想的不太一样。通常,我会把一些长时间运行的任务用下面的方式包裹起来
try:
...
except KeyboardInterrupt:
#close the script cleanly here
这样在按下 Ctrl-C
时,任务可以正常关闭。但这次在运行这个特定的脚本时,按下 Ctrl-C
后,脚本直接退出了,没有捕捉到 Ctrl-C
。
最初的版本是用 multiprocessing
的 Process
实现的。我又用 threading
的 Thread
重写了脚本,但问题依旧。我之前用过 threading
很多次,但对 multiprocessing
这个库还是比较陌生。不管怎样,我从来没有遇到过这种 Ctrl-C
的行为。
通常,我总是会实现一些信号量等来有序关闭 Queues
和 Thread
实例,但这个脚本却直接退出,没有任何反应。
最后,我尝试像这样重写 signal.SIGINT
def handler(signal, frame):
print 'Ctrl+C'
signal.signal(signal.SIGINT, handler)
...
在这里,Ctrl+C
确实被捕捉到了,但处理程序没有执行,什么也没有打印出来。
除了 threading
和 multiprocessing
的部分,脚本中还包含了一些 C++
的 SWIG
对象。我不知道这是否有关系。我在 OS X Lion 上运行的是 Python 2.7.2。
所以,我有几个问题:
- 这到底是怎么回事?
- 我该如何调试这个问题?
- 我需要学习什么才能理解根本原因?
请注意:脚本的内部实现是专有的,所以我不能提供代码示例。不过,我非常愿意接受一些建议,以便我自己进行调试。我有足够的经验,如果有人能指点我正确的方向,我应该能搞明白。
编辑:我开始注释掉一些导入等,看看是什么导致了这个奇怪的行为,最后我发现是导入了一个 C++ SWIG
库。有没有人知道为什么导入一个 C++ SWIG
库会“抢走” Ctrl-C
?不过我并不是这个有问题的库的作者,而且我的 SWIG 经验有限,所以不知道从哪里开始...
编辑 2:我刚在一台 Windows 机器上试了同样的脚本,在 Windows 7 上,Ctrl-C
按照预期被捕捉到了。我不打算再纠结于 OS X 的部分,反正这个脚本会在 Windows 环境下运行。
3 个回答
关于 atexit 这个模块怎么样呢? 这里有个链接可以了解更多信息
程序退出是因为可能有其他地方在处理键盘中断(比如按下CTRL+C),然后抛出了其他异常,或者直接返回了None。你应该能看到一个错误追踪信息,这样可以帮助你调试。你需要捕获错误输出,或者用命令行的-i选项运行你的脚本,这样你就能看到错误追踪信息了。此外,建议再加一个except块来捕获所有其他的异常。
如果你怀疑C++函数调用在处理CTRL+C,可以尝试捕获它的输出。如果这个C函数没有返回任何东西,那你能做的也不多,只能请求作者添加一些异常处理、返回码等。
try:
#Doing something proprietary ...
#catch the function call output
result = yourCFuncCall()
#raise an exception if it's not what you expected
if result is None:
raise ValueError('Unexpected Result')
except KeyboardInterupt:
print('Must be a CTRL+C')
return
except:
print('Unhandled Exception')
raise
这可能和Python处理线程、信号以及C语言调用的方式有关。
简单来说,按Ctrl-C无法中断C语言的调用,因为这个操作需要一个Python线程来处理信号,而这个线程必须是主线程(通常会被阻塞,等待其他线程完成)。
实际上,长时间运行的操作可能会阻塞所有东西。
想想这个情况:
>>> nums = xrange(100000000)
>>> -1 in nums
False (after ~ 6.6 seconds)
>>>
现在,试着按Ctrl-C(无法中断!)
>>> nums = xrange(100000000)
>>> -1 in nums
^C^C^C (nothing happens, long pause)
...
KeyboardInterrupt
>>>
Ctrl-C在多线程程序中不起作用的原因是,主线程通常会被一个无法中断的线程连接或锁住(比如任何的'wait'、'join',或者只是一个空的'main'线程,这在后台会导致Python等待任何新创建的线程)。
试着在你的主线程中插入一个简单的
while True:
time.sleep(1)
。
如果你有一个长时间运行的C语言函数,最好在C语言层面处理信号(愿原力与你同在!)。
这主要基于David Beazley的一个视频。