需求
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