python lib concurrent.futures

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的存在,同时存在调度开销。
多进程执行的耗时较短

猜你喜欢

转载自www.cnblogs.com/wodeboke-y/p/12815196.html