雷猴啊,兄弟们!今天来展示一下如何用Python快速实现一个线程池。
python实现一个线程池
一、序言
当有多个 IO 密集型的任务要被处理时,我们自然而然会想到多线程。但如果任务非常多,我们不可能每一个任务都启动一个线程去处理,这个时候最好的办法就是实现一个线程池,至于池子里面的线程数量可以根据业务场景进行设置。
比如我们实现一个有 10 个线程的线程池,这样可以并发地处理 10 个任务,每个线程将任务执行完之后,便去执行下一个任务。通过使用线程池,可以避免因线程创建过多而导致资源耗尽,而且任务在执行时的生命周期也可以很好地把控。
而线程池的实现方式也很简单,但这里我们不打算手动实现,因为 Python 提供了一个标准库 concurrent.futures,已经内置了对线程池的支持。所以本篇文章,我们就来详细介绍一下该模块的用法。
二、正文
1、Future 对象
当我们往线程池里面提交一个函数时,会分配一个线程去执行,同时立即返回一个 Future 对象。通过 Future 对象可以监控函数的执行状态,有没有出现异常,以及有没有执行完毕等等。如果函数执行完毕,内部便会调用 future.set_result 将返回值设置到 future 里面,然后外界便可调用 future.result 拿到返回值。
除此之外 future 还可以绑定回调,一旦函数执行完毕,就会以 future 为参数,自动触发回调。所以 future 被称为未来对象,可以把它理解为函数的一个容器,当我们往线程池提交一个函数时,会立即创建相应的 future 然后返回。函数的执行状态什么的,都通过 future 来查看,当然也可以给它绑定一个回调,在函数执行完毕时自动触发。
那么下面我们就来看一下 future 的用法,文字的话理解起来可能有点枯燥。
将函数提交到线程池里面运行时,会立即返回一个对象
这个对象就叫做 Future 对象,里面包含了函数的执行状态等等
当然我们也可以手动创建一个Future对象。
from concurrent.futures import Future
# 创建 Future 对象 future
future = Future()
# 给 future 绑定回调
def callback(f: Future):
print("当set_result的时候会执行回调,result:",
f.result())
future.add_done_callback(callback)
# 通过 add_done_callback 方法即可给 future 绑定回调
# 调用的时候会自动将 future 作为参数
# 如果需要多个参数,那么就使用偏函数
# 回调函数什么时候执行呢?
# 显然是当 future 执行 set_result 的时候
# 如果 future 是向线程池提交函数时返回的
# 那么当函数执行完毕时会自动执行 future.set_result(xx)
# 并将自身的返回设置进去
# 而这里的 future 是我们手动创建的,因此需要手动执行
future.set_result("嘿嘿")
当set_result的时候会执行回调,result: 嘿嘿
需要注意的是:只能执行一次 set_result,但是可以多次调用 result 获取结果。
from concurrent.futures import Future
future = Future()
future.set_result("哼哼")
print(future.result()) # 哼哼
print(future.result()) # 哼哼
print(future.result()) # 哼哼
执行 future.result() 之前一定要先 set_result,否则会一直处于阻塞状态。当然 result 方法还可以接收一个 timeout 参数,表示超时时间,如果在指定时间内没有获取到值就会抛出异常。
2、提交函数自动创建 Future 对象
我们上面是手动创建的 Future 对象,但工作中很少会手动创建。我们将函数提交到线程池里面运行的时候,会自动创建 Future 对象并返回。这个 Future 对象里面就包含了函数的执行状态,比如此时是处于暂停、运行中还是完成等等,并且函数在执行完毕之后,还会调用 future.set_result 将自身的返回值设置进去。
from concurrent.futures import ThreadPoolExecutor
import time
def task(name, n):
time.sleep(n)
return f"{
name} 睡了 {
n} 秒"
# 创建一个线程池
# 里面还可以指定 max_workers 参数,表示最多创建多少个线程
# Python学习交流裙279199867
# 如果不指定,那么每提交一个函数,都会为其创建一个线程
executor = ThreadPoolExecutor()
# 通过 submit 即可将函数提交到线程池,一旦提交,就会立刻运行
# 因为开启了一个新的线程,主线程会继续往下执行
# 至于 submit 的参数,按照函数名,对应参数提交即可
# 切记不可写成task("古明地觉", 3),这样就变成调用了
future = executor.submit(task, "屏幕前的你", 3)
# 由于函数里面出现了 time.sleep,并且指定的 n 是 3
# 所以函数内部会休眠 3 秒,显然此时处于运行状态
print(future)
"""
<Future at 0x7fbf701726d0 state=running>
"""
# 我们说 future 相当于一个容器,包含了内部函数的执行状态
# 函数是否正在运行中
print(future.running())
"""
True
"""
# 函数是否执行完毕
print(future.done())
"""
False
"""
# 主程序也 sleep 3 秒
time.sleep(3)
# 显然此时函数已经执行完毕了
# 并且打印结果还告诉我们返回值类型是 str
print(future)
"""
<Future at 0x7fbf701726d0 state=finished returned str>
"""
print(future.running())
"""
False
"""
print(future.done())
"""
True
"""
# 函数执行完毕时,会将返回值设置在 future 里
# 也就是说一旦执行了 future.set_result
# 那么就表示函数执行完毕了,然后外界可以调用 result 拿到返回值
print(future.result())
"""
屏幕前的你 睡了 3 秒
"""
这里再强调一下 future.result(),这一步是会阻塞的,举个例子:
# 提交函数
future = executor.submit(task, "屏幕前的你", 3)
start = time.perf_counter()
future.result(