转载请注明出处:https://blog.csdn.net/jpch89/article/details/87643972
concurrent.futures
是 3.2
中引入的新模块,它为异步执行可调用对象提供了高层接口。
可以使用 ThreadPoolExecutor
来进行多线程编程,ProcessPoolExecutor
进行多进程编程,两者实现了同样的接口,这些接口由抽象类 Executor
定义。
这个模块提供了两大类型,一个是执行器类 Executor
,另一个是 Future
类。
执行器用来管理工作池,future
用来管理工作计算出来的结果,通常不用直接操作 future
对象,因为有丰富的 API
。
concurrent.futures.Executor
类
这个抽象类提供了一系列方法,可以用于异步执行调用。
它不能直接使用,只能通过子类化出来的具体类来使用。
它定义的方法有:
submit(fn, *args, **kwargs)
安排可调用对象 fn
以 fn(*args, **kwargs)
的形式执行,并返回 Future
对象来表示它的执行。
with ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(pow, 323, 1235)
print(future.result())
map(func, *iterables, timeout=None, chunksize=1)
类似内置函数 map(func, *iterables)
,但是有两点不同:
iterables
而不会惰性获取;func
,并支持多次并发调用。它返回一个迭代器。
从调用 Executor.map()
开始的 timeout
秒之后,如果在迭代器上调用了 __next__()
并且无可用结果的话,迭代器会抛出 concurrent.futures.TimeoutError
异常。
timeout
秒数可以是浮点数或者整数,如果设置为 None
或者不指定,则不限制等待时间。
如果 func
调用抛出了异常,那么该异常会在从迭代器获取值的时候抛出。
当使用 ProcessPoolExecutor
的时候,这个方法会把 iterables
划分成多个块,作为独立的任务提交到进程池。这些块的近似大小可以通过给 chunksize
指定一个正整数。对于很长的 iterables
,使用较大的 chunksize
而不是采用默认值 1
,可以显著提高性能。对于 ThreadPoolExecutor
,chunksize
不起作用。
chunksize
是3.5
加入的新参数。
注意:不管并发任务的执行次序如何,map
总是基于输入顺序来返回值。map
返回的迭代器,在主程序迭代的时候,会等待每一项的响应。
shutdown(wait=True)
告诉执行器 executor
在当前所有等待的 future
对象运行完毕后,应该释放执行器用到的所有资源。
在 shutdown
之后再调用 Executor.submit()
和 Executor.map()
会报运行时错误 RuntimeError
。
如果 wait
为 True
,那么这个方法会在所有等待的 future
都执行完毕,并且属于执行器 executor
的资源都释放完之后才会返回。
如果 wait
为 False
,本方法会立即返回。属于执行器的资源会在所有等待的 future
执行完毕之后释放。
不管 wait
取值如何,整个 Python
程序在等待的 future
执行完毕之前不会退出。
你可以通过 with
语句来避免显式调用本方法。with
语句会用 wait=True
的默认参数调用 Executor.shutdown()
方法。
import shutil
with ThreadPoolExecutor(max_workers=4) as e:
e.submit(shutil.copy, 'src1.txt', 'dest1.txt')
e.submit(shutil.copy, 'src2.txt', 'dest2.txt')
e.submit(shutil.copy, 'src3.txt', 'dest3.txt')
e.submit(shutil.copy, 'src4.txt', 'dest4.txt')
执行器类 Executor
实现了上下文协议,可以用做上下文管理器。它能并发执行任务,等待它们全部完成。当上下文管理器退出时,自动调用 shutdown()
方法。
ThreadPoolExecutor
线程池执行器是 Executor
执行器的子类,通过线程池来执行异步调用。它管理一组工作线程,当工作线程有富余的时候,给它们传递任务。
当属于一个 Future
对象的可调用对象等待另一个 Future
的返回时,会发生死锁 deadlock
。
举个例子:
import time
def wait_on_b():
time.sleep(5)
print(b.result()) # b will never complete because it is waiting on a.
return 5
def wait_on_a():
time.sleep(5)
print(a.result()) # a will never complete because it is waiting on b.
return 6
executor = ThreadPoolExecutor(max_workers=2)
a = executor.submit(wait_on_b)
b = executor.submit(wait_on_a)
再举一例:
def wait_on_future():
f = executor.submit(pow, 5, 2)
# This will never complete because there is only one worker thread and
# it is executing this function.
print(f.result())
executor = ThreadPoolExecutor(max_workers=1)
executor.submit(wait_on_future)
concurrent.futures.ThreadPoolExecutor(max_workers=None, thread_name_prefix='', initializer=None, initargs=())
这个 Executor
子类最多用 max_workers
个线程来异步执行调用。
initializer
是一个可选的可调用对象,会在每个 worker
线程启动之前调用。
initargs
是传递给 initializer
的参数元组。
如果 initializer
抛出了异常,那么当前所有等待的任务都会抛出 BrokenThreadPool
异常,继续提交 submit
任务也会抛出此异常。
3.5
的变化:如果 max_worker
没有指定或者为 None
,则默认为本机处理器数量乘以 5
。
3.6
新特性:添加了 thread_name_prefix
参数,可以控制由线程池创建的工作线程名称,便于调试。
3.7
的变化:添加了 initializer
和 initargs
参数。
import concurrent.futures
import urllib.request
URLS = ['http://www.foxnews.com/',
'http://www.cnn.com/',
'http://europe.wsj.com/',
'http://www.bbc.co.uk/',
'http://some-made-up-domain.com/']
# Retrieve a single page and report the URL and contents
def load_url(url, timeout):
with urllib.request.urlopen(url, timeout=timeout) as conn:
return conn.read()
# We can use a with statement to ensure threads are cleaned up promptly
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
# Start the load operations and mark each future with its URL
future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
data = future.result()
except Exception as exc:
print('%r generated an exception: %s' % (url, exc))
else:
print('%r page is %d bytes' % (url, len(data)))
ProcessPoolExecutor
进程池执行器类是 Executor
执行器类的子类,使用进程池来异步执行调用。
ProcessPoolExecutor
使用了 multiprocessing
模块,这允许它可以规避 Global Interpreter Lock
,但是也意味着只能执行和返回可序列化的(picklable
)对象。
__main__
模块必须被 worker
子进程导入,这意味着 ProcessPoolExecutor
在交互解释器中无法工作。
在已经被提交到 ProcessPoolExecutor
中的可调用对象内使用 Executor
或者 Future
方法会导致死锁。
concurrent.futures.ProcessPoolExecutor(max_workers=None, mp_context=None, initializer=None, initargs=())
这个 Executor
子类最多用 max_workers
个进程来异步执行调用。
如果不指定 max_workers
或者为 None
,它默认为本机的处理器数量。
如果 max_workers
小于等于 0
,会抛出 ValueError
异常。
mp_context
是多进程上下文(multiprocessing context
)或者 None
,它会被用来启动 workers
。如果不指定 mp_context
或者为 None
,会使用默认的多进程上下文环境。
initializer
是一个可选的可调用对象,会在每个 worker
进程启动之前调用。
initargs
是传递给 initializer
的参数元组。
如果 initializer
抛出了异常,那么当前所有等待的任务都会抛出 BrokenProcessPool
异常,继续提交 submit
任务也会抛出此异常。
3.3
版本的变化:任意一个工作进程突然中止时,会抛出 BrokenProcessPool
异常。之前版本中,行为是未定义的,而且对于执行器或者它的 future
对象的操作通常会无响应或者死锁。
3.7
版本的变化:加入了 mp_context
参数,允许用户控制由进程池创建的工作进程的 start_method
方法。该版本还加入了 initializer
和 initargs
参数。
import concurrent.futures
import math
PRIMES = [
112272535095293,
112582705942171,
112272535095293,
115280095190773,
115797848077099,
1099726899285419]
def is_prime(n):
if n % 2 == 0:
return False
sqrt_n = int(math.floor(math.sqrt(n)))
for i in range(3, sqrt_n + 1, 2):
if n % i == 0:
return False
return True
def main():
with concurrent.futures.ProcessPoolExecutor() as executor:
for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
print('%d is prime: %s' % (number, prime))
if __name__ == '__main__':
main()
Future
类封装了可调用对象的异步执行。
Future
实例通过 Executor.submit()
创建。
concurrent.futures.Future
封装了可调用对象的异步执行。
Future
实例通过 Executor.submit()
创建,除非用于测试,不应该直接手动创建。
cancel()
尝试取消调用,如果该调用正在执行中,无法取消,本方法返回 False
,其他情况下调用会被取消,并返回 True
。理解:当一个
Future
实例已经被提交但是还没开始执行时,可以通过调用该实例的cancel()
方法取消。
cancelled()
如果调用已经被成功取消,返回 True
。running()
如果调用正在执行,无法被取消,则返回 True
。done()
如果调用成功被取消或者已经执行完毕,返回 True
。result(timeout=None)
返回调用的返回值。如果调用还没有完成,则最多等待 timeout
秒。如果 timeout
秒之后还没有完成,抛出 concurrent.futures.TimeoutError
。timeout
可以为整数或者浮点数。如果不指定或者为 None
,则不限制等待时间。如果 future
在完成之前被取消了,会抛出 CancelledError
异常。理解:
result()
阻塞直到任务完成,或者被取消。如果需要按照顺序访问结果,使用执行器的map
方法,如果不需要按照顺序访问结果,可以使用模块函数as_completed()
。
exception(timeout=None)
timeout
秒。如果 timeout
秒之后还没有完成,抛出 concurrent.futures.TimeoutError
。timeout
可以为整数或者浮点数。如果不指定或者为 None
,则不限制等待时间。future
在完成之前被取消了,会抛出 CancelledError
异常。None
。add_done_callback(fn)
future
附加可调用对象 fn
。当 future
运行完毕或者被取消时,它会被用作 fn
的唯一参数,并调用 fn
。Exception
子类的异常,它会被记录并忽略。如果它抛出了属于 BaseException
子类的异常,该行为未定义。future
已经完成或者已经取消,fn
会被立即调用。理解:这个方法不用显式地等待结果返回,可以事先指定当
Future
完成之后应当执行的调用。fn
是一个单参数的可调用对象,使用传入的Future
对象之前最好先检查它的状态,因为不管是正常结束、抛出异常还是被取消,Future
对象都会被认为执行完毕。
concurrent.futures.wait(fs, timeout=None, return_when=ALL_COMPLETED)
等待 Future
实例完成,这些实例可能由多个不同的执行器实例创建,通过 fs
指定这些 Future
实例。返回具名元组,该元组有两个元素,每个元素都是一个集合。第一个元素名叫 done
,该集合包括已完成的 futures
;第二个元素名叫 not_done
,该集合包括未完成的 futures
。
timeout
用来控制返回之前等待的最大秒数,可以是整数或者浮点数。如果不指定或为 None
,不限制等待时间。
return_when
指明函数何时应该返回。它必须是下列常量之一:
FIRST_COMPLETED
:函数在任意一个 future
完成或者被取消时返回。FIRST_EXCEPTION
:函数在任意一个 future
因为异常而结束时返回。如果没有 future
抛出异常,它等价于 ALL_COMPLETED
。ALL_COMPLETED
:当所有 future
完成或者被取消时函数才会返回。concurrent.futures.as_completed(fs, timeout=None)
当通过 fs
指定的 Future
实例全部执行完毕或者被取消后,返回这些 Future
实例组成的迭代器。fs
中的 Future
实例可以被不同的执行器创建。任何在 as_completed()
调用之前就已经完成的 Future
实例会被最先生成。
查看源码发现,实际上这是一个用到了
yield from
的生成器函数,所以调用返回一个生成器。
如果从 as_completed()
调用开始,经过 timeout
秒之后,对返回的迭代器调用 __next__()
时结果仍不可用,则会抛出 concurrent.futures.TimeoutError
异常。timeout
可以是整数或者浮点数,如果 timeout
没有指定或者为 None
,则不限制等待时间。
Future
内部方法:用于单元测试和实现自定义执行器的方法。
set_running_or_notify_cancel()
set_result(result)
set_exception(exception)
完成于 2019.02.18