如果任何线程有异常,导致python退出

时间:2018-04-05 02:17:28

标签: multithreading python-3.x

我有一个python3程序,它启动第二个线程(除了主线程)以异步处理某些事件。理想情况下,我的程序没有缺陷,并且从未有过未处理的异常。但事情发生了。当/如果有异常,我希望整个解释器以错误代码退出,就好像它是一个单独的线程一样。这可能吗?

现在,如果在生成的线程上发生异常,它会输出通常的错误信息,但不会退出。主线程一直在继续。

实施例

import threading
import time

def countdown(initial):
    while True:
        print(initial[0])
        initial = initial[1:]
        time.sleep(1)

if __name__ == '__main__':
    helper = threading.Thread(target=countdown, args=['failsoon'])
    helper.start()
    time.sleep(0.5)
    #countdown('THISWILLTAKELONGERTOFAILBECAUSEITSMOREDATA')
    countdown('FAST')

countdown最终无法从字符串中访问[0],因为它已被清空而导致IndexError: string index out of range错误。目标是主要或帮助程序首先死亡,整个程序一共死亡,但堆栈跟踪信息仍然输出。

尝试解决方案

经过一番挖掘,我的想法是使用sys.excepthook。我添加了以下内容:

def killAll(etype, value, tb):
    print('KILL ALL')
    traceback.print_exception(etype, value, tb)
    os.kill(os.getpid(), signal.SIGKILL)

sys.excepthook = killAll

如果主线程是先死掉的线程,这是有效的。但在另一种情况下却没有。这似乎是一个已知问题(https://bugs.python.org/issue1230540)。我会尝试一些解决方法。

虽然示例显示了我创建的主线程和辅助线程,但我对一般情况感兴趣,我可能正在运行启动线程的其他人的库。

2 个答案:

答案 0 :(得分:2)

好吧,你可以简单地在你的线程中引发一个错误并让主线程处理并报告该错误。从那里你甚至可以终止程序。

例如,在您的工作线程上:

try:
    self.result = self.do_something_dangerous()
except Exception as e:
    import sys
    self.exc_info = sys.exc_info()

并在主线程上:

if self.exc_info:
    raise self.exc_info[1].with_traceback(self.exc_info[2])
return self.result

为了给您一个更完整的图片,您的代码可能如下所示:

import threading

class ExcThread(threading.Thread):
    def excRun(self):
        pass
        #Where your core program will run

    def run(self):
        self.exc = None
        try:
        # Possibly throws an exception
            self.excRun()
        except:
            import sys
            self.exc = sys.exc_info()
            # Save details of the exception thrown 
            # DON'T rethrow,
            # just complete the function such as storing
            # variables or states as needed

    def join(self):
        threading.Thread.join(self)
        if self.exc:
            msg = "Thread '%s' threw an exception: %s" % (self.getName(), self.exc[1])
            new_exc = Exception(msg)
            raise new_exc.with_traceback(self.exc[2])

(我添加了一个额外的行来跟踪哪个线程导致错误,如果你有多个线程,那么命名它们也是一个好习惯)

答案 1 :(得分:1)

我的解决方案最终成为了solution posted hereSIGKILL解决方案之间的幸福婚姻。我在我的包中添加了以下killall.py子模块:

import threading
import sys
import traceback
import os
import signal


def sendKillSignal(etype, value, tb):
    print('KILL ALL')
    traceback.print_exception(etype, value, tb)
    os.kill(os.getpid(), signal.SIGKILL)


original_init = threading.Thread.__init__
def patched_init(self, *args, **kwargs):
    print("thread init'ed")
    original_init(self, *args, **kwargs)
    original_run = self.run
    def patched_run(*args, **kw):
        try:
            original_run(*args, **kw)
        except:
            sys.excepthook(*sys.exc_info())
    self.run = patched_run


def install():
    sys.excepthook = sendKillSignal
    threading.Thread.__init__ = patched_init

然后在启动任何其他线程之前立即运行install(我自己的创建或其他导入的库)。