Python多线程捕获子线程的异常,并退出主进程。
自己在项目的开发中,一般能避免在单个进程中使用多线程就尽量把每个线程包装成独立的进程执行,通过socket或者一些中间件比如redis进行通讯,工作,协调。
但有时候必须涉及到多线程操作,而且碰到的情况中,多个线程必须协调全部正常工作才能执行逻辑,但子线程有着自己的栈区,报错了并不影响其它的线程,导致整个进程无法退出。
我当时想到的有两种思路,一种是多个线程间进行通讯或者一个全局变量的标记,当报错的时候,就修改这个标记,所有的子线程定时去查询这个标记,但感觉这个思路的拓展性太差,而且每个子线程需要主动定期查询或者通讯,太麻烦了。
后面一种就是我准备上代码的思路, 将所有的子线程设计成守护线程,主线程循环查询子线程的状态值,当发现任意的子线程状态异常,获取该子线程的异常对象,并上浮,退出主线程,导致所有的子线程退出。
import threading, traceback import time class ExcThread(threading.Thread): def __init__(self, call_obj, *args, **kwargs): super(ExcThread, self).__init__(*args, **kwargs) self.callable_obj = call_obj # 自己设置的退出状态值 self.exit_code = 0 self.exception = None self.exc_traceback = '' # 主动设置为守护线程,必须条件 self.setDaemon(True) def run(self): try: self._run() except Exception as e: self.exit_code = 1 # 存储异常对象保存在实例对象中 self.exception = e self.exc_traceback = traceback.format_exc() def _run(self): try: self.callable_obj(*self._args, **self._kwargs) except Exception as e: raise e def t_func(name, age=18): while 1: print(name, age) time.sleep(3) if age == 1: raise Exception('hee') # 生成一份子线程列表对象,用于主线程轮询检查使用 def start_child_thread(): thread_task_list = [] for i in range(3): f = ExcThread(call_obj=t_func, args=('sidian',), kwargs={'age': i}) f.start() thread_task_list.append(f) return thread_task_list def check_thread(): t_list = start_child_thread() while 1: for task in t_list: if not task.is_alive(): raise task.exception time.sleep(1) if __name__ == '__main__': check_thread()
2022年2月22日更新。
由于项目的改版,重新需要用到多线程,个人定制版的Thread
class ExcThread(threading.Thread): def __init__(self, target=None, *args, name=None, **kwargs): if name is None: name = target.__name__ super(ExcThread, self).__init__(None, target, name, args, kwargs) self.exit_code = None self.exception = None self.exc_traceback = '' # 默认为守护线程 self.setDaemon(True) def run(self): try: self._run() except Exception as e: self.exception = e self.exit_code = 1 self.exc_traceback = traceback.format_exc() else: self.exit_code = 0 def _run(self): try: self._target(*self._args, **self._kwargs) except Exception as e: raise e
主线程巡查各子线状态
# 主线程检测子线程运行,接受到子线程死亡信号,上浮子线程错误信息 def _check_child_thread_status(self): while True: for task in self._thread_task_list.copy(): # 已经完成的任务删除 if not task.is_alive(): self._thread_task_list.remove(task) if task.exit_code: print(f'{datetime.datetime.now()} task: {task.name} raise exception.', file=sys.stderr) print(f'{datetime.datetime.now()} task: {task.name} raise exception.') try: raise task.exception except: l = logger_sd(stream_display=False, file_out=False, mail_send=True) l.critical(f'*-smt-*\n {traceback.format_exc()} \n *-smt-*') print(traceback.format_exc()) raise time.sleep(.1)