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

Tra*_*ggs 11 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')
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

如果主线程是第一个死亡的线程,则此方法有效。但在另一种情况下则不然。这似乎是一个已知问题(https://bugs.python.org/issue1230540)。我会在那里尝试一些解决方法。

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

Har*_*eem 6

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

例如在你的工作线程上:

try:
    self.result = self.do_something_dangerous()
except Exception as e:
    import sys
    self.exc_info = sys.exc_info()
Run Code Online (Sandbox Code Playgroud)

并在主线程上:

if self.exc_info:
    raise self.exc_info[1].with_traceback(self.exc_info[2])
return self.result
Run Code Online (Sandbox Code Playgroud)

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

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])
Run Code Online (Sandbox Code Playgroud)

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


Tra*_*ggs 3

我的解决方案最终成为此处发布的解决方案SIGKILL上面的解决方案之间的幸福结合。我将以下子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
Run Code Online (Sandbox Code Playgroud)

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