Python多线程捕获子线程的异常,并退出主进程。
自己在项目的开发中,一般能避免在单个进程中使用多线程就尽量把每个线程包装成独立的进程执行,通过socket或者一些中间件比如redis进行通讯,工作,协调。
但有时候必须涉及到多线程操作,而且碰到的情况中,多个线程必须协调全部正常工作才能执行逻辑,但子线程有着自己的栈区,报错了并不影响其它的线程,导致整个进程无法退出。
我当时想到的有两种思路,一种是多个线程间进行通讯或者一个全局变量的标记,当报错的时候,就修改这个标记,所有的子线程定时去查询这个标记,但感觉这个思路的拓展性太差,而且每个子线程需要主动定期查询或者通讯,太麻烦了。
后面一种就是我准备上代码的思路, 将所有的子线程设计成守护线程,主线程循环查询子线程的状态值,当发现任意的子线程状态异常,获取该子线程的异常对象,并上浮,退出主线程,导致所有的子线程退出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | 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)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
2020-12-24 分享一个小工具,通过Python的脚本中已经赋值的变量名,生成字典dict。
2020-12-24 super的日常使用小记.
2019-12-24 流畅的python,Fluent Python 第七章笔记