打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Python3标准库:concurrent.futures管理并发任务池

1. concurrent.futures管理并发任务池

concurrent.futures模块提供了使用工作线程或进程池运行任务的接口。线程和进程池的API是一样的,所以应用只做最小的修改就可以在线程和进程之间顺利地切换。

这个模块提供了两种类型的类与这些池交互。执行器(executor)用来管理工作线程或进程池,future用来管理工作线程或进程计算的结果。要使用一个工作线程或进程池,应用要创建适当的执行器类的一个实例,然后向它提交任务来运行。每个任务启动时,会返回一个Future实例。需要任务的结果时,应用可以使用Future阻塞,直到得到结果。目前已经提供了不同的API,可以很方便地等待任务完成,所以不需要直接管理Future对象。

1.1 利用基本线程池使用map()

ThreadPooLExecutor管理一组工作线程,当这些线程可用于完成更多工作时,可以向它们传入任务。下面的例子使用map()并发地从一个输入迭代器生成一组结果。这个任务使用time.sleep()暂停不同的时间,从而展示不论任务的执行顺序如何,map()总是根据输入按顺序返回值。

from concurrent import futuresimport threadingimport timedef task(n):    print('{}: sleeping {}'.format(        threading.current_thread().name,        n)    )    time.sleep(n / 10)    print('{}: done with {}'.format(        threading.current_thread().name,        n)    )    return n / 10ex = futures.ThreadPoolExecutor(max_workers=2)print('main: starting')results = ex.map(task, range(5, 0, -1))print('main: unprocessed results {}'.format(results))print('main: waiting for real results')real_results = list(results)print('main: results: {}'.format(real_results))

map()的返回值实际上是一种特殊类型的迭代器,它知道主程序迭代处理时要等待各个响应。

 1.2 调度单个任务

除了使用map(),还可以借助submit()利用一个执行器调度单个任务。然后可以使用返回的Future实例等待这个任务的结果。

from concurrent import futuresimport threadingimport timedef task(n):    print('{}: sleeping {}'.format(        threading.current_thread().name,        n)    )    time.sleep(n / 10)    print('{}: done with {}'.format(        threading.current_thread().name,        n)    )    return n / 10ex = futures.ThreadPoolExecutor(max_workers=2)print('main: starting')f = ex.submit(task, 5)print('main: future: {}'.format(f))print('main: waiting for results')result = f.result()print('main: result: {}'.format(result))print('main: future after result: {}'.format(f))

任务完成之后,Future的状态会改变,并得到结果。

1.3 按任意顺序等待任务

调用Future的result()方法会阻塞,直到任务完成(可能返回一个值,也可能抛出一个异常)或者撤销。可以使用map()按调度任务的顺序访问多个任务的结果。如果处理结果的顺序不重要,则可以使用as_completed()在每个任务完成时处理它的结果。

from concurrent import futuresimport randomimport timedef task(n):    time.sleep(random.random())    return (n, n / 10)ex = futures.ThreadPoolExecutor(max_workers=5)print('main: starting')wait_for = [    ex.submit(task, i)    for i in range(5, 0, -1)]for f in futures.as_completed(wait_for):    print('main: result: {}'.format(f.result()))

因为池中的工作线程与任务同样多,故而所有任务都可以启动。它们会按随机的顺序完成,所以每次运行这个示例程序时as_completed()生成的值都不同。

1.4 回调

要在任务完成时采取某个动作,不用显式地等待结果,可以使用add_done_callback()指示Future完成时要调用一个新函数。这个回调应当是有一个参数(Future实例)的callable函数。

from concurrent import futuresimport timedef task(n):    print('{}: sleeping'.format(n))    time.sleep(0.5)    print('{}: done'.format(n))    return n / 10def done(fn):    if fn.cancelled():        print('{}: canceled'.format(fn.arg))    elif fn.done():        error = fn.exception()        if error:            print('{}: error returned: {}'.format(                fn.arg, error))        else:            result = fn.result()            print('{}: value returned: {}'.format(                fn.arg, result))if __name__ == '__main__':    ex = futures.ThreadPoolExecutor(max_workers=2)    print('main: starting')    f = ex.submit(task, 5)    f.arg = 5    f.add_done_callback(done)    result = f.result()

不论由于什么原因,只要认为Future“完成”,就会调用这个回调,所以在使用它之前必须检查传入回调的对象的状态。

1.5 撤销任务

如果一个Future已经提交但还没启动,那么可以调用它的cancel()方法将其撤销。

from concurrent import futuresimport timedef task(n):    print('{}: sleeping'.format(n))    time.sleep(0.5)    print('{}: done'.format(n))    return n / 10def done(fn):    if fn.cancelled():        print('{}: canceled'.format(fn.arg))    elif fn.done():        print('{}: not canceled'.format(fn.arg))if __name__ == '__main__':    ex = futures.ThreadPoolExecutor(max_workers=2)    print('main: starting')    tasks = []    for i in range(10, 0, -1):        print('main: submitting {}'.format(i))        f = ex.submit(task, i)        f.arg = i        f.add_done_callback(done)        tasks.append((i, f))    for i, t in reversed(tasks):        if not t.cancel():            print('main: did not cancel {}'.format(i))    ex.shutdown()

cancel()返回一个布尔值,指示任务是否可用撤销。

1.6 任务中的异常

如果一个任务产生一个未处理的异常,那么它会被保存到这个任务的Future,而且可以通过result()或exception()方法得到。

from concurrent import futuresdef task(n):    print('{}: starting'.format(n))    raise ValueError('the value {} is no good'.format(n))ex = futures.ThreadPoolExecutor(max_workers=2)print('main: starting')f = ex.submit(task, 5)error = f.exception()print('main: error: {}'.format(error))try:    result = f.result()except ValueError as e:    print('main: saw error "{}" when accessing result'.format(e))

如果在一个任务函数中抛出一个未处理的异常后调用了result(),那么会在当前上下文中再次抛出同样的异常。

1.7 上下文管理器

执行器会与上下文管理器合作,并发的运行任务并等待它们都完成。当上下文管理器退出时,会调用执行器的shutdown()方法。

from concurrent import futuresdef task(n):    print(n)with futures.ThreadPoolExecutor(max_workers=2) as ex:    print('main: starting')    ex.submit(task, 1)    ex.submit(task, 2)    ex.submit(task, 3)    ex.submit(task, 4)print('main: done')

离开当前作用域时如果要清理线程或进程资源,那么用这种方式使用执行器就很有用。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
应用技巧:python编程四种多线程应用,你知道几种?
一篇文章浅析Python自带的线程池和进程池
提升代码效率:掌握Python中并行for循环从入门到精通
Python的多进程并行计算库与多进程爬虫
只需几行代码,即可实现多线程和多进程操作
python协程系列(五)——asyncio的核心概念与基本架构
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服