python主线程捕获子线程异常

本文探讨了如何在Python多线程编程中处理子线程异常,通过使用queue模块实现异常传递和线程状态监控。作者介绍了queue的不同类型及其在异常管理中的应用,最终解决了主线程不受子线程异常影响的问题。
摘要由CSDN通过智能技术生成

需求

python多线程时,子线程出现异常,主线程依然正常向下执行,显然不符合工程代码,为解决子线程异常问题,新增今天的内容:捕获子线程异常。

问题

直接看代码:

def thread_text(i):
    time.sleep(i)
    raise Exception(u'error')
    
def main():
    t = threading.Thread(target=thread_text, args=(1,))
    t.start()
    t.join()
    print(u'end')
if __name__ =='__main__':
    main()

这是一个简单的测试,在子线程中返回一个异常,输出如下:

end

Exception in thread Thread-1:
Traceback (most recent call last):
  File "/usr/lib/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.6/threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "/usercode/file.py", line 8, in thread_text
    raise Exception(u'error')
Exception: error

可以看到,虽然子线程报出了异常,但main方法依然正常打印出了‘end’,这显然不是我们预期的,接下来,我们开始解决

queue介绍

queue是python3的内置模块,创建堆栈队列,用来处理多线程通信,主要方法如下:

  • queue.Queue(maxsize=0)
    先进先出(First In First Out: FIFO)队列,
    入参 maxsize 是一个整数,用于设置队列的最大长度。一旦队列达到上限,插入数据将会被阻塞,直到有数据出队列之后才可以继续插入。如果 maxsize 设置为小于或等于零,则队列的长度没有限制。
import queue
q = queue.Queue()  # 创建 Queue 队列
for i in range(3):
    q.put(i)  # 在队列中依次插入0、1、2元素
for i in range(3):
    print(q.get())  # 依次从队列中取出插入的元素,数据元素输出顺序为0、1、2
  • queue.LifoQueue(maxsize=0)
    后进先出(Last In First Out: LIFO)队列,最后进入队列的数据拥有出队列的优先权,就像栈一样。

  • PriorityQueue(maxsize=0)
    优先级队列,比较队列中每个数据的大小,值最小的数据拥有出队列的优先权。数据一般以元组的形式插入,典型形式为(priority_number, data)。如果队列中的数据没有可比性,那么数据将被包装在一个类中,忽略数据值,仅仅比较优先级数字。

我们这里只用优先级队列Queue就够了,毕竟只是需要知道异常与否,方法有如下几个:

  • Queue.qsize()
    返回队列中数据元素的个数。
  • Queue.empty()
    如果队列为空,返回 True,否则返回 False。
  • Queue.full()
    如果队列中元素个数达到上限,返回 True,否则返回 False。
  • Queue.put(item, block=True, timeout=None)
    item,放入队列中的数据元素。
    block,当队列中元素个数达到上限继续往里放数据时:如果 block=False,直接引发 queue.Full 异常;如果 block=True,且 timeout=None,则一直等待直到有数据出队列后可以放入数据;如果 block=True,且 timeout=N,N 为某一正整数时,则等待 N 秒,如果队列中还没有位置放入数据就引发 queue.Full 异常。
    timeout,设置超时时间。
  • Queue.put_nowait(item)
    相当于 Queue.put(item, block=False),当队列中元素个数达到上限继续往里放数据时直接引发 queue.Full 异常。
  • Queue.get(block=True, timeout=None)
    从队列中取出数据并返回该数据内容。
    block,当队列中没有数据元素继续取数据时:如果 block=False,直接引发 queue.Empty 异常;如果 block=True,且 timeout=None,则一直等待直到有数据入队列后可以取出数据;如果 block=True,且 timeout=N,N 为某一正整数时,则等待 N 秒,如果队列中还没有数据放入的话就引发 queue.Empty 异常。
    timeout,设置超时时间。
  • Queue.get_nowait()
    相当于 Queue.get(block=False)block,当队列中没有数据元素继续取数据时直接引发 queue.Empty 异常。

我们主要使用的是queue.get/queue.put/queue.empty
思路为:当子线程异常,则向queue添加一条数据,可以是任何数据,main方法检测queue是否为空,如果是空的,说明子线程无异常,反之则异常,然后通过检测活跃的线程数来确定子线程是否执行完毕。

检测线程相关代码

threading.enumerate()  # list 当前活跃的线程,包含主线程
threading.active_count()  # int 当前活跃线程数,包含主线程

输出如下

[<_MainThread(MainThread, started 8628338176)>]
1

好了,思路及用到的模块都介绍完毕,直接上代码

import queue
import threading
import sys
def thread_text(q, i):
    try:
        time.sleep(i)
        raise Exception(u'error')
    except:
        q.put(sys.exc_info())
    # time.sleep(1)
    
if __name__ =='__main__':
    q = queue.Queue()
    t = threading.Thread(target=thread_text, args=(q, 1))
    t.start()
    while True:
        
        if q.empty():
            if threading.active_count() == 1:
                break
        else:
            print(q.get())
            raise Exception(u'main error')
    print(u'end')

输出如下:

(<class 'NameError'>, NameError("name 'time' is not defined",), <traceback object at 0x7f7a1b2625c8>)

		
Traceback (most recent call last):
  File "/usercode/file.py", line 25, in <module>
    raise Exception(u'main error')
Exception: main error

可以看到,并没有输出‘end’, 而是返回‘main error’, 说明主线程成功捕获到子线程,
而这里我是在子线程里queue.put了sys模块的exc_info(),能够更方便查错改错。

到这里问题就解决完毕了。

END

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值