前文演示了多线程模块threading的基本使用,在下面启动多线程任务时使用了如下的代码
task_list = []
for i in range(loop_count):
# 实例化线程任务,把count_num函数的运行交给子线程运行
task = threading.Thread(target=count_num, args=(i,))
task_list.append(task)
for task in task_list:
# 将任务取出来,调用start()方法真正开始运行子线程
task.start()
上述代码执行的结果,是有多少个任务就后台启动多少线程,一个任务一个线程,任务处理完线程就销毁。虽然Python有GIL机制的限制,但是试想一下如果有100个线程的任务需要运行。那么反复创建、切换、销毁线程,CPU资源的开销也很大。因此理想的状态是,不管队列里有多少个任务,我们创建的线程数量是固定的,这些已创建的线程去队列里获取剩余任务,直至队列清空,在保障并发的同时,限制资源用量。
ThreadPoolExecutor模块,官方内置的线程池工具。
要注意使用这个模块,需要从 concurrent 包中引入。它不在 threading基础包中。
import time
import random
import threading
# 导入ThreadPoolExecutor
from concurrent.futures import ThreadPoolExecutor
def myfunc(i):
# 获取当前线程的标识符
thread_id = threading.current_thread().ident
# 随机等待一段时间,模拟任务执行时间
random_sleep_time = random.randint(5, 10)
print(f'线程 [ {thread_id} ],任务 < {i} > 启动, 随机等待时间为 {random_sleep_time} 秒')
time.sleep(random_sleep_time)
print(f'线程 [ {thread_id} ],任务 < {i} > 结束')
# 通过max_workers参数,限制线程数的数量
with ThreadPoolExecutor(max_workers=2) as executor:
futures = []
# 提交10个任务
for i in range(10):
# 任务编号从1开始
i = i + 1
# 提交任务到线程池执行
future = executor.submit(myfunc, i)
futures.append(future)
for future in futures:
# 等待所有任务完成
future.result()
运行结果:
(venv) % python w2.py
# 同时启动了2个线程
线程 [ 6172979200 ],任务 < 1 > 启动, 随机等待时间为 6 秒
线程 [ 6189805568 ],任务 < 2 > 启动, 随机等待时间为 6 秒
# 因为任务<1> 和 <2> 都是6秒完成运行,所以2个线程同时结束任务,并分别获取任务<3>和<4>
线程 [ 6172979200 ],任务 < 1 > 结束
线程 [ 6172979200 ],任务 < 3 > 启动, 随机等待时间为 9 秒
线程 [ 6189805568 ],任务 < 2 > 结束
线程 [ 6189805568 ],任务 < 4 > 启动, 随机等待时间为 6 秒
线程 [ 6189805568 ],任务 < 4 > 结束
线程 [ 6189805568 ],任务 < 5 > 启动, 随机等待时间为 6 秒
线程 [ 6172979200 ],任务 < 3 > 结束
线程 [ 6172979200 ],任务 < 6 > 启动, 随机等待时间为 6 秒
线程 [ 6189805568 ],任务 < 5 > 结束
线程 [ 6189805568 ],任务 < 7 > 启动, 随机等待时间为 7 秒
# 任务<8> 耗时较长,线程 [ 6172979200 ]被长时间占用
线程 [ 6172979200 ],任务 < 6 > 结束
线程 [ 6172979200 ],任务 < 8 > 启动, 随机等待时间为 9 秒
# 线程 [ 6189805568 ] 连续处理了任务<9>和<10>
线程 [ 6189805568 ],任务 < 7 > 结束
线程 [ 6189805568 ],任务 < 9 > 启动, 随机等待时间为 5 秒
线程 [ 6189805568 ],任务 < 9 > 结束
线程 [ 6189805568 ],任务 < 10 > 启动, 随机等待时间为 7 秒
线程 [ 6172979200 ],任务 < 8 > 结束
线程 [ 6189805568 ],任务 < 10 > 结束
(venv) %
从上述运行过程可以看到,从始至终只有2个线程在处理队列里的任务。并在一个线程被长时间占用时,另一个线程持续进行工作。