「这是我参与2022首次更文挑战的第23天,活动详情查看:2022首次更文挑战」。
正式的Python专栏第72篇,同学站住,别错过这个从0开始的文章!
在去年的时候,学委写了多线程很多demo,那时候写线程的代码都是用threading.Thread这个类。
本篇学委将展示另一种线程的创建方式,当然它是通过线程池来创建,并被它管理。
ThreadPoolExecutor 线程池
学委写了这么多年Java代码,看到Python有这个并不稀奇。
ThreadPoolExecutor 名字非常直接,线程池执行器。
executor = ThreadPoolExecutor() #启动线程池
print("executor:", executor)
也就是说,它管理了一组线程(一个或者多个线程),同时也是一个线程的执行器。
执行器何意?
也就是说,不需要像Thread().start()那样,这个执行器可以帮我们执行线程。
这就是submit方法。它的作用跟线程的start方法类似,提交但不保证马上执行,根据程序内资源情况安排执行线程,一般不繁忙情况下,立刻得到执行。
学委准备了下面的demo代码:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2022/2/23 10:20 下午
# @Author : LeiXueWei
# @CSDN/Juejin/Wechat: 雷学委
# @XueWeiTag: CodingDemo
# @File : executordemo.py
# @Project : DeepDivePython
import threading
import time
from concurrent.futures.thread import ThreadPoolExecutor
from datetime import datetime
def do_something(args):
name = threading.currentThread().name
print("%s thread-name %s - args %s" % (datetime.now(), name, args))
time.sleep(1)
print("%s thread-name %s - done" % (datetime.now(), name))
return "end of " + name
executor = ThreadPoolExecutor()
print("executor:", executor)
for i in range(10):
executor.submit(do_something, ("学委的Executor Demo " + str(i)))
# executor.map(do_something, [("学委的Executor Demo " + str(i)) for i in range(10)])
这个程序编写了一个do_something方法,内部为线程的具体任务。
然后提交10个线程,直接把参数打印出来。
map方法
这个方法有点像map算子,但又不完全是,或者说是一类map计算。
通常我们看到的map计算是这样的
IterableObject.map(element -> conversion(e))
但是这个方法是
executor.map(线程目标函数, [] #一个数据集合)
下面的代码跟第一段代码一样的效果,但是看起来更加简洁了。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2022/2/23 10:20 下午
# @Author : LeiXueWei
# @CSDN/Juejin/Wechat: 雷学委
# @XueWeiTag: CodingDemo
# @File : executordemo2.py
# @Project : DeepDivePython
import threading
import time
from concurrent.futures.thread import ThreadPoolExecutor
from datetime import datetime
def do_something(args):
name = threading.currentThread().name
print("%s thread-name %s - args %s" % (datetime.now(), name, args))
time.sleep(1)
print("%s thread-name %s - done" % (datetime.now(), name))
return "end of " + name
executor = ThreadPoolExecutor()
print("executor:", executor)
executor.map(do_something, [("学委的Executor Demo " + str(i)) for i in range(10)])
shutdown 方法
ThreadPoolExecutor除了对外开放了submit/map方法,还有一个shutdown方法,用来关闭线程池。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2022/2/23 10:20 下午
# @Author : LeiXueWei
# @CSDN/Juejin/Wechat: 雷学委
# @XueWeiTag: CodingDemo
# @File : executordemo.py
# @Project : DeepDivePython
import threading
import time
from concurrent.futures.thread import ThreadPoolExecutor
from datetime import datetime
STAT_DICT = {}
def do_something(args):
name = threading.currentThread().name
STAT_DICT[name] = 'start'
print("%s thread-name %s - args %s" % (datetime.now(), name, args))
time.sleep(3)
print("%s thread-name %s - done" % (datetime.now(), name))
STAT_DICT[name] = 'done'
return "end of " + name
executor = ThreadPoolExecutor()
print("executor:", executor)
executor.map(do_something, [("学委的Executor Demo " + str(i)) for i in range(10)])
print("will shutdown executor")
executor.shutdown(wait=False)
print("after shutdown - executor:", executor)
def monitor():
for i in range(50):
time.sleep(1)
print("学委Monitor:",STAT_DICT)
#executor.submit(monitor) #雷学委温馨提示:关闭的线程池不允许再次提交线程任务。#RuntimeError: cannot schedule new futures after shutdown
ThreadPoolExecutor().submit(monitor)
运行效果如下:
(题外话:这里的text-to-gif工具,是学委过年的时候开发的,欢迎使用 :-P )
这里我们看到,尽管调用了shutdown方法。已经submit的线程还继续执行。
但是executor会标记池为关闭状态,再次调用submit将会出错。
总结
本篇学委分享了并发库的ThreadPoolExecutor类,在开发中我们可以使用它的submit/map等方法提交任务。
当然线程池创建的时候还可以定制化,我们后面再谈。
通过展示,我们看到线程池管理了一组线程,如果我们调用shutdown,会触发相关资源回收。
比手动创建线程更加专业。
从今天开始,如果程序需要用到很多线程处理的,想想是否可以用线程池来管理它们。
放任程序中随意创建线程,随着时间推移,可能后续程序员也会有开辟新线程的需要。
这样长期累积下来,程序会出现很多线程,最终有一天,无法管理这些线程。
因为可能是不同人创建的,维护起来非常困难。
通常我们会用线程池(一个或者多个)但是不会像手动创建线程那样随机,来给应用开辟一个弹性多任务执行的缓冲区。由这些线程池,专门来处理多线程的工作,统一管理。同时在应用退出前调用这些池的shutdown方法,回收线程池相关资源。这个后续会深度解析一下。
喜欢Python的朋友,请关注学委的 Python基础专栏 or Python入门到精通大专栏
持续学习持续开发,我是雷学委!
编程很有趣,关键是把技术搞透彻讲明白。
欢迎关注微信,点赞支持收藏!