python lib concurrent.futures
1. 简介
concurrent是python官方包,目前其中只包含concurrent.futures这一个包。
Source code: Lib/concurrent/futures/thread.py and Lib/concurrent/futures/process.py
它为异步执行方法提供了高级别的调用接口。
ThreadPoolExecutor:线程执行类
ProcessPoolExecutor:进程执行类
Executor:上述两者的接口类。
这个模块提供了两大类型,一个是执行器类 Executor,另一个是 Future 类。
执行器用来管理工作池,future 用来管理工作计算出来的结果,一般无需直接操作future 对象。
2. 详细解说
2.1. Executor Objects
class concurrent.futures.Executor
抽象类,提供接口方法。
它不能直接使用。
方法:
2.1.1. submit(fn, *args, **kwargs)
执行fn(*args, **kwargs)
返回futures对象。
2.1.2. map(func, *iterables, timeout=None, chunksize=1)
类似于map(func, *iterables)
但它是立即获取所有的迭代对象而不是一一执行。
func异步执行,并可同时执行多个。
如果迭代器经过timeout仍未抛出对象,raises concurrent.futures.TimeoutError
如果func执行异常,这个异常对象会在取值时抛出。
使用ProcessPoolExecutor时,chunksize决定每批提交的func数量。当任务队列非常长时,设为较大值会比设为1快得多。它对ThreadPoolExecutor无效。
2.1.3. shutdown(wait=True)
在当前futures队列完成后释放资源。当它被调用后再调用Executor.submit() and Executor.map()会raise 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() 方法。
2.2. ThreadPoolExecutor 线程池执行器
concurrent.futures.ThreadPoolExecutor(max_workers=None, thread_name_prefix='', initializer=None, initargs=())
ThreadPoolExecutor 线程池执行器是 Executor 执行器的子类,通过线程池来执行异步调用。它管理一组工作线程,当工作线程有富余的时候,开始新任务。
max_workers:线程池的最大容量。默认cpu核数*5
initializer:可选的可调用对象,在每个worker线程启动之前调用。
initargs 是传递给 initializer 的参数元组。
如果 initializer 抛出了异常,那么当前所有等待的任务都会抛出 BrokenThreadPool 异常,继续提交 submit 任务也会抛出此异常。
thread_name_prefix:工作线程名称前缀,便于调试。
有两种情况会发生死锁
- Future对象a调用Future对象b,但b依赖于a的返回, deadlock。
- Future对象a调用b,但因为线程池容量限制,b不可能被执行;也会产生死锁。
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)
2.3. ProcessPoolExecutor
底层使用multiprocessing,带来的结果就是只能传递可序列化的对象。
在交互模式下无法使用它。
class concurrent.futures.ProcessPoolExecutor(max_workers=None, mp_context=None, initializer=None, initargs=())
max_workers:工作进程数量限制,默认为本机cpu核心数。
mp_context:上下文
initializer 可调用对象,在每个worker进程启动之前调用。
initargs 是传递给 initializer 的参数元组。
如果 initializer 抛出异常,那么当前所有等待的任务都会抛出 BrokenProcessPool 异常,继续提交 submit 任务也会抛出此异常。
2.4. Future 对象
Future 类封装了可调用对象的异步执行过程。
它的实例通过Executor.submit() 创建。
class concurrent.futures.Future
方法:
cancel():正在执行的对象无法取消,此时返回false;否则取消成功返回True。
cancelled():返回状态
running()
done()
result(timeout=None):返回执行结果。如果未执行完成,timeout确定等待时间;
如果等待超时,抛出concurrent.futures.TimeoutError。
如果future已取消,返回CancelledError。
如果调用抛出异常,此方法返回该异常。
exception(timeout=None)
add_done_callback(fn):添加回调,到期执行fn(future)。
还有一些调试用接口:
set_running_or_notify_cancel()
set_result(result)
set_exception(exception)
2.5. 模块函数
concurrent.futures.wait(fs, timeout=None, return_when=ALL_COMPLETED)
类似于asyncio的wait返回Returns a named 2-tuple of sets,名称为done和not_done。
concurrent.futures.as_completed(fs, timeout=None)
与前者类似
import concurrent.futures as fu 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() if __name__ == '__main__': pass pool = fu.ProcessPoolExecutor(max_workers=3) fu_list = {pool.submit(load_url, url, 60): url for url in URLS} print(fu_list) done, not_done = fu.wait(fu_list, 55) print(done, not_done)
2.6. 异常类
模板异常类。
- exception concurrent.futures.CancelledError
- exception concurrent.futures.TimeoutError
- exception concurrent.futures.BrokenExecutor
- exception concurrent.futures.InvalidStateError
- exception concurrent.futures.thread.BrokenThreadPool
- exception concurrent.futures.process.BrokenProcessPool
3. 测试
3.1. 单线程/多线程/多进程执行的性能测试
import concurrent import concurrent.futures as fu import time # 求最大公约数 def gcd(pair): a, b = pair low = min(a, b) for i in range(low, 0, -1): if a % i == 0 and b % i == 0: return i numbers = [ (1963309, 2265973), (1879675, 2493670), (2030677, 3814172), (1551645, 2229620), (1988912, 4736670), (2198964, 7876293) ] # 测试执行时间前的准备工作 from processing_time import test_func para = (numbers,) # 测试函数1 # 单线程 def no_concurrent(ar): for x in ar: print('{}与{}的最小公约数为:{}'.format(x[0], x[1], gcd(x))) #gcd(x) # 测试函数2 # 多线程 def threadpoolexecutor(ar): pool = concurrent.futures.ThreadPoolExecutor(max_workers=3) res = list(pool.map(gcd, numbers)) print(res) # 测试函数3 # 多进程ProcessPoolExecutor def processpoolexecutor(ar): pool = concurrent.futures.ProcessPoolExecutor(max_workers=3) res = list(pool.map(gcd, numbers)) print(res) #processpoolexecutor() #test_func(processpoolexecutor, numbers) if __name__ == '__main__': pass #time.sleep(60) test_func(no_concurrent, numbers, process_times=1) test_func(threadpoolexecutor, numbers, process_times=1) test_func(processpoolexecutor, numbers) #processpoolexecutor(5) pass
3.1.1. 执行结果
开始测试函数: 函数:no_concurrent 执行时长: 1.117450402 开始测试函数 : 函数:threadpoolexecutor 执行时长: 1.1176262840000002 开始测试函数 : 函数:processpoolexecutor 执行时长: 0.8564167989999998
多线程耗时比单线程长是因为gil的存在,同时存在调度开销。
多进程执行的耗时较短