使用futures模块处理并发
concurrent.futures模块的主要特色是ThreadPoolExecutor和ProcessPoolExecutor类,这两个类实现的接口能分别在不同的线程或进程中执行可调用的对象。这两个类在内部维护着一个工作线程或进程池,以及要执行的任务队列。ThreadPoolExecutor和ProcessPoolExecutor的API接口一样,本文重点讲解ThreadPoolExecutor的用法。
一、用法示例
1. 使用 map() 处理多个任务
使用两行代码,就能帮我们调用多线程完成任务:
with futures.ThreadPoolExecutor(max_workers=5) as executor: # max_workers 最大线程数
res = executor.map(fn, params) # fn 是被调用函数,params 是 fn 的参数
例子如下:
from concurrent import futures
def download_source(url):
print(url)
return url * 100
urls = range(1, 20)
with futures.ThreadPoolExecutor(max_workers=5) as executor:
res = executor.map(download_source, urls)
print(list(res))
1
2
3
4
... # 省略了输出
18
19
[100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900]
注意事项: map() 返回结果的顺序和调用开始的顺序一直,如果第一个调用生成结果用时10秒,而其他调用只用1秒,代码会阻塞10秒以获取第一个结果。在此之后,获取后续结果不会阻塞,因为后续调用已经结束。更可取的方式是,不管提交顺序如何,只要有结果就获取。而使用 Executor.submit 方法和 futures.as_completed 函数可以帮我们完成这种方式。
2. 使用 submit() 提交单个任务
map() 可以帮我们快速完成一批任务,但是我们有时候需要的是单个处理,可以使用 submit():
import time
from concurrent import futures
def sleep_and_print(number):
time.sleep(1)
print('I am number', number)
return number * 100
numbers = range(1, 100)
with futures.ThreadPoolExecutor(max_workers=5) as executor:
for num in numbers:
future = executor.submit(sleep_and_print, num) # 当submit时,executor会创建线程,并将事件加入_work_queue(threading.Queue),多个线程从_work_queue获取Work并执行。
此处 submit() 返回的 future 并不是结果,而是指待完成的操作。future 封装待完成的操作,可以放入队列,完成的状态可以查询,得到结果后可以获取结果(或者异常)。
3. 使用 as_completed() 逐个获取已完成的任务
as_completed() 返回一个包含 fs 所指定的
Future
实例(可能由不同的Executor
实例创建)的迭代器,这些实例会在完成时生成 future 对象(包括正常结束或被取消的 future 对象)。 任何由 fs 所指定的重复 future 对象将只被返回一次。 任何在as_completed()
被调用之前完成的 future 对象将优先被生成。 如果