Python 并发库之 认识 ThreadPoolExecutor

821 阅读4分钟

「这是我参与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个线程,直接把参数打印出来。

屏幕快照 2022-02-23 下午11.20.50.png

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)

运行效果如下:

executorshutdowndemo.gif

(题外话:这里的text-to-gif工具,是学委过年的时候开发的,欢迎使用 :-P )

这里我们看到,尽管调用了shutdown方法。已经submit的线程还继续执行。

但是executor会标记池为关闭状态,再次调用submit将会出错。

总结

本篇学委分享了并发库的ThreadPoolExecutor类,在开发中我们可以使用它的submit/map等方法提交任务。

当然线程池创建的时候还可以定制化,我们后面再谈。

通过展示,我们看到线程池管理了一组线程,如果我们调用shutdown,会触发相关资源回收。

比手动创建线程更加专业。

从今天开始,如果程序需要用到很多线程处理的,想想是否可以用线程池来管理它们

放任程序中随意创建线程,随着时间推移,可能后续程序员也会有开辟新线程的需要。

这样长期累积下来,程序会出现很多线程,最终有一天,无法管理这些线程。

因为可能是不同人创建的,维护起来非常困难。

通常我们会用线程池(一个或者多个)但是不会像手动创建线程那样随机,来给应用开辟一个弹性多任务执行的缓冲区。由这些线程池,专门来处理多线程的工作,统一管理。同时在应用退出前调用这些池的shutdown方法,回收线程池相关资源。这个后续会深度解析一下。

喜欢Python的朋友,请关注学委的 Python基础专栏 or Python入门到精通大专栏

持续学习持续开发,我是雷学委!
编程很有趣,关键是把技术搞透彻讲明白。
欢迎关注微信,点赞支持收藏!