理解Python并发编程-PoolExecutor篇

本文介绍Python标准库concurrent.futures模块中ThreadPoolExecutor和ProcessPoolExecutor类的使用方法,通过实例展示如何利用这些工具简化多线程和多进程任务的管理,包括异常处理和异步调用。

摘要: 之前我们使用多线程(threading)和多进程(multiprocessing)完成常规的需求,在启动的时候start、jon等步骤不能省,复杂的需要还要用1-2个队列。随着需求越来越复杂,如果没有良好的设计和抽象这部分的功能层次,代码量越多调试的难度就越大。

之前我们使用多线程(threading)和多进程(multiprocessing)完成常规的需求,在启动的时候start、jon等步骤不能省,复杂的需要还要用1-2个队列。随着需求越来越复杂,如果没有良好的设计和抽象这部分的功能层次,代码量越多调试的难度就越大。有没有什么好的方法把这些步骤抽象一下呢,让我们不关注这些细节,轻装上阵呢?

答案是:有的。

从Python3.2开始一个叫做concurrent.futures被纳入了标准库,而在Python2它属于第三方的futures库,需要手动安装:


pip install futures

```                                             


这个模块中有2个类:ThreadPoolExecutor和ProcessPoolExecutor,也就是对threading和multiprocessing的进行了高级别的抽象,

暴露出统一的接口,帮助开发者非常方便的实现异步调用:


```python

import time

from concurrent.futures import ProcessPoolExecutor, as_completed


NUMBERS = range(25, 38)



def fib(n):

    if n<= 2:

        return 1

    return fib(n-1) + fib(n-2)



start = time.time()


with ProcessPoolExecutor(max_workers=3) as executor:

    for num, result in zip(NUMBERS, executor.map(fib, NUMBERS)):

        print 'fib({}) = {}'.format(num, result)


print 'COST: {}'.format(time.time() - start)


感受下是不是很轻便呢?看一下花费的时间:


 python fib_executor.py

fib(25) = 75025

fib(26) = 121393

fib(27) = 196418

fib(28) = 317811

fib(29) = 514229

fib(30) = 832040

fib(31) = 1346269

fib(32) = 2178309

fib(33) = 3524578

fib(34) = 5702887

fib(35) = 9227465

fib(36) = 14930352

fib(37) = 24157817

COST: 10.8920350075


除了用map,另外一个常用的方法是submit。如果你要提交的任务的函数是一样的,就可以简化成map。但是假如提交的任务函数是不一样的,或者执行的过程之可能出现异常(使用map执行过程中发现问题会直接抛出错误)就要用到submit:


from concurrent.futures import ThreadPoolExecutor, as_completed


NUMBERS = range(30, 35)



def fib(n):

    if n == 34:

        raise Exception("Don't do this")

    if n<= 2:

        return 1

    return fib(n-1) + fib(n-2)



with ThreadPoolExecutor(max_workers=3) as executor:

    future_to_num = {executor.submit(fib, num): num for num in NUMBERS}

    for future in as_completed(future_to_num):

        num = future_to_num[future]

        try:

            result = future.result()

        except Exception as e:

            print 'raise an exception: {}'.format(e)

        else:

            print 'fib({}) = {}'.format(num, result)



with ThreadPoolExecutor(max_workers=3) as executor:

    for num, result in zip(NUMBERS, executor.map(fib, NUMBERS)):

        print 'fib({}) = {}'.format(num, result)


执一下:


python fib_executor_with_raise.py

fib(30) = 832040

fib(31) = 1346269

raise an exception: Don't do this

fib(32) = 2178309

fib(33) = 3524578

Traceback (most recent call last):

  File "fib_executor_with_raise.py", line 28, in <module>

    for num, result in zip(NUMBERS, executor.map(fib, NUMBERS)):

  File "/Library/Python/2.7/site-packages/concurrent/futures/_base.py", line 580, in map

    yield future.result()

  File "/Library/Python/2.7/site-packages/concurrent/futures/_base.py", line 400, in result

    return self.__get_result()

  File "/Library/Python/2.7/site-packages/concurrent/futures/_base.py", line 359, in __get_result

    reraise(self._exception, self._traceback)

  File "/Library/Python/2.7/site-packages/concurrent/futures/_compat.py", line 107, in reraise

    exec('raise exc_type, exc_value, traceback', {}, locals_)

  File "/Library/Python/2.7/site-packages/concurrent/futures/thread.py", line 61, in run

    result = self.fn(*self.args, **self.kwargs)

  File "fib_executor_with_raise.py", line 9, in fib

    raise Exception("Don't do this")

Exception: Don't do this


可以看到,第一次捕捉到了异常,但是第二次执行的时候错误直接抛出来了。

上面说到的map,有些同学马上会说,这不是进程(线程)池的效果吗?看起来确实是的:


import time

from multiprocessing.pool import Pool


NUMBERS = range(25, 38)



def fib(n):

    if n<= 2:

        return 1

    return fib(n-1) + fib(n-2)



start = time.time()


pool = Pool(3)

results = pool.map(fib, NUMBERS)

for num, result in zip(NUMBERS, pool.map(fib, NUMBERS)):

    print 'fib({}) = {}'.format(num, result)


print 'COST: {}'.format(time.time() - start)


好像代码量更小哟。好吧,看一下花费的时间:


 

python fib_pool.py

fib(25) = 75025

fib(26) = 121393

fib(27) = 196418

fib(28) = 317811

fib(29) = 514229

fib(30) = 832040

fib(31) = 1346269

fib(32) = 2178309

fib(33) = 3524578

fib(34) = 5702887

fib(35) = 9227465

fib(36) = 14930352

fib(37) = 24157817

COST: 17.1342718601


 

WhatTF竟然花费了1.7倍的时间。为什么?

BTW,有兴趣的同学可以对比下ThreadPool和ThreadPoolExecutor,由于GIL的缘故,对比的差距一定会更多。

原理

我们就拿ProcessPoolExecutor介绍下它的原理,引用官方代码注释中的流程图:


|======================= In-process =====================|== Out-of-process ==|


+----------+     +----------+       +--------+     +-----------+    +---------+

|          |  => | Work Ids |    => |        |  => | Call Q    | => |         |

|          |     +----------+       |        |     +-----------+    |         |

|          |     | ...      |       |        |     | ...       |    |         |

|          |     | 6        |       |        |     | 5, call() |    |         |

|          |     | 7        |       |        |     | ...       |    |         |

| Process  |     | ...      |       | Local  |     +-----------+    | Process |

|  Pool    |     +----------+       | Worker |                      |  #1..n  |

| Executor |                        | Thread |                      |         |

|          |     +----------- +     |        |     +-----------+    |         |

|          | <=> | Work Items | <=> |        | <=  | Result Q  | <= |         |

|          |     +------------+     |        |     +

转载于:https://www.cnblogs.com/jzy996492849/p/7054079.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值