Python并发concurrent.futures和asyncio

说明

Python标准库为我们提供了threading和multiprocessing模块编写相应的多线程/多进程代码。

从Python3.2开始,标准库为我们提供了concurrent.futures模块,concurrent.futures 模块的主要特色是 ThreadPoolExecutor 和
ProcessPoolExecutor 类,这两个类实现的接口能分别在不同的线程或进程中执行可调
用的对象。这两个类在内部维护着一个工作线程或进程池,以及要执行的任务队列。

Python 3.4 以后标准库中asyncio 包,这个包使用事件循环驱动的协程实现并发。这是 Python 中最大也
是最具雄心壮志的库之一。asyncio 大量使用 yield from 表达式,因此与
Python 旧版不兼容。

submit和map方法

submit方法作用是向线程池提交可回调的task,并返回一个回调实例。
example:

import time
from concurrent.futures import ThreadPoolExecutor


# 可回调的task
def pub_task(msg):
    time.sleep(3)
    return msg

# 创建一个线程池
pool = ThreadPoolExecutor(max_workers=3)

# 往线程池加入2个task
task1 = pool.submit(pub_task, 'a')
task2 = pool.submit(pub_task, 'b')

print(task1.done())        # False
time.sleep(4)
print(task2.done())        # True

print(task1.result())
print(task2.result())

map方法是创建一个迭代器,回调的结果有序放在迭代器中。

问题:

Executor.map 函数易于使用,不过有个特性可能有用,也可能没用,具体情况取决于需
求:这个函数返回结果的顺序与调用开始的顺序一致。如果第一个调用生成结果用时 10
秒,而其他调用只用 1 秒,代码会阻塞 10 秒,获取 map 方法返回的生成器产出的第一个
结果。在此之后,获取后续结果时不会阻塞,因为后续的调用已经结束。如果必须等到获
取所有结果后再处理,这种行为没问题;不过,通常更可取的方式是,不管提交的顺序,
只要有结果就获取。为此,要把 Executor.submit 方法和 futures.as_completed 函
数结合起来使用。

from concurrent.futures import ThreadPoolExecutor
import requests

URLS = ['http://www.csdn.com', 'http://qq.com', 'http://www.leasonlove.cn']


def task(url, timeout=10):
    return requests.get(url, timeout=timeout)

pool = ThreadPoolExecutor(max_workers=3)
results = pool.map(task, URLS)

for ret in results:
    print('%s, %s' % (ret.url, ret))

future异步编程

Future可以理解为一个在未来完成的操作,这是异步编程的基础。通常情况下,我们执行io操作,访问url时(如下)在等待结果返回之前会产生阻塞,cpu不能做其他事情,而Future的引入帮助我们在等待的这段时间可以完成其他的操作。

from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import as_completed
import requests

URLS = ['http://www.csdn.cn', 'http://qq.com', 'http://www.leasonlove.cn']


def task(url, timeout=1):
    return requests.get(url, timeout=timeout)


with ThreadPoolExecutor(max_workers=3) as executor:
    future_tasks = [executor.submit(task, url) for url in URLS]

    for f in future_tasks:
        if f.running():
            print('%s is running' % str(f))

    for f in as_completed(future_tasks):
        try:
            ret = f.done()
            if ret:
                f_ret = f.result()
                print('%s, done, result: %s, %s' % (str(f), f_ret.url, f_ret.content))
        except Exception as e:
            # 第一个url无响应
            f.cancel()
            print(str(e))

asyncio库协程实现并发

对于gevent 和 asyncio

建议大家放弃Gevent,拥抱asyncio,asyncio是Python3.4以后标准库。而且由于Gevent直接修改标准库里面大部分的阻塞式系统调用,包括socket、ssl、threading和 select等模块,而变为协作式运行。但是我们无法保证你在复杂的生产环境中有哪些地方使用这些标准库会由于打了补丁而出现奇怪的问题。

import asyncio
import time
start = time.time()

async def do(x):
    print('Waiting: ', x)
    await asyncio.sleep(x)
    return 'Finish after {}s'.format(x)


task1 = do(1)
task2 = do(2)
task3 = do(4)

tasks = [
    asyncio.ensure_future(task1),
    asyncio.ensure_future(task2),
    asyncio.ensure_future(task3)
]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

for task in tasks:
    print('Task result: ', task.result())

end = time.time()
print('TIME: ', end - start)

协程与线程

如果使用线程做过重要的编程,你就知道写
出程序有多么困难,因为调度程序任何时候都能中断线程。必须记住保留锁,去保护程序
中的重要部分,防止多步操作在执行的过程中中断,防止数据处于无效状态。
而协程默认会做好全方位保护,以防止中断。我们必须显式产出才能让程序的余下部分运
行。对协程来说,无需保留锁,在多个线程之间同步操作,协程自身就会同步,因为在任
意时刻只有一个协程运行。想交出控制权时,可以使用 yield 或 yield from 把控制权
交还调度程序。这就是能够安全地取消协程的原因:按照定义,协程只能在暂停的 yield
处取消,因此可以处理 CancelledError 异常,执行清理操作。

参考资料:fluent Python

leason | blog

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

leason00

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值