Python中如何使用Future、asyncio处理并发?

本文探讨了并发的意义,强调并发与并行的区别,并介绍了Python中的Future概念,包括concurrent.futures模块和asyncio包。文章通过示例详细讲解了如何使用Future进行并发操作,解释了Executor的submit、map方法以及Future的状态查询和结果获取。同时,讨论了Python中的GIL和ProcessPoolExecutor,展示了如何使用它们处理CPU密集型任务。最后,文章提到了asyncio包处理并发的方式,如协程和事件循环,并给出了asyncio与aiohttp结合使用示例,以及如何防止阻塞事件循环。
摘要由CSDN通过智能技术生成

并发的意义

为了高效处理网络I/O,需要使用并发,因为网络有很高的延迟,所以为了不浪费CPU周期去等待,最好在收到网络响应之前做些其他的事。

在I/O密集型应用中,如果代码写得正确,那么不管是用哪种并发策略(使用线程或asyncio包),吞吐量都比依序执行的代码高很多。
并发是指一次处理多件事。并行是指一次做多件事。一个关于结构,一个关于执行。

并行才是我们通常认为的那个同时做多件事情,而并发则是在线程这个模型下产生的概念。

并发表示同时发生了多件事情,通过时间片切换,哪怕只有单一的核心,也可以实现“同时做多件事情”这个效果。

根据底层是否有多处理器,并发与并行是可以等效的,这并不是两个互斥的概念。

举个我们开发中会遇到的例子,我们说资源请求并发数达到了1万。这里的意思是有1万个请求同时过来了。但是这里很明显不可能真正的同时去处理这1万个请求的吧!

如果这台机器的处理器有4个核心,不考虑超线程,那么我们认为同时会有4个线程在跑。

也就是说,并发访问数是1万,而底层真实的并行处理的请求数是4。

如果并发数小一些只有4的话,又或者你的机器牛逼有1万个核心,那并发在这里和并行一个效果。

也就是说,并发可以是虚拟的同时执行,也可以是真的同时执行。而并行的意思是真的同时执行。

结论是:并行是我们物理时空观下的同时执行,而并发则是操作系统用线程这个模型抽象之后站在线程的视角上看到的“同时”执行。

Future

一、初识future

concurrent.futures 模块主要特色是:ThreadPoolEXecutor和 ProcessPoolExecutor类,这两个类实现的接口能分别在不同的线程或进程中执行可调用的对象。

这两个类在内部维护着一个工作线程或进程池,以及要执行的任务队列。

from concurrent import futures

MAX_WORKERS = 20

def download_many():

    workers = min(MAX_WORKERS,len(url_list))
    with futures.ThreadPoolExecutor(workers) as executor:
        res = executor.map(download_one,sorted(url_list))
    return len(list(res))

(1)设定工作的线程数量,使用允许的最大值与要处理的数量之间的较小的那个值,以免创建过于的线程。

(2)download_one函数在多个线程中并发调用,map方法返回一个生成器,因此可以迭代,获取各个函数返回的值。

future是concurrent.futures模块和asyncio包的重要组件。

从python3.4开始标准库中有两个名为Future的类:concurrent.futures.Future和asyncio.Future

这两个类的作用相同:两个Future类的实例都表示可能完成或者尚未完成的延迟计算。与Twisted中的Deferred类、Tornado框架中的Future类的功能类似

future封装待完成的操作,可以放入队列,完成的状态可以查询,得到结果(或抛出异常)后可以获取结果(或异常)。

▲ 通常情况下自己不应该创建future,只能由并发框架(concurrent.future或asyncio)实例化。

future表示终将发生的事情,而确定某件事会发生的唯一方式就是执行的时间已经排定。

只有排定把某件事交给concurrent.futures.Executor子类处理时,才会创建concurrent.futures.Future实例。

**Executor.submit(fn, *args, kwargs)

Executor.submit() 方法的参数是一个可调用的对象,调用这个方法后会为传入的可调用对象排期,返回一个future。

▲ 不是阻塞的,而是立即返回。能够使用 done()方法判断该任务是否结束。

使用cancel()方法可以取消提交的任务,如果任务已经在线程池中运行了,就取消不了。

客户端代码不应该改变future的状态,并发框架在future表示的延迟计算结束后会改变future状态。而我们无法控制计算何时结束。

Executor.shutdown(wait=True)

释放系统资源,在Executor.submit()或 Executor.map()等异步操作后调用。使用with语句可以避免显式调用此方法。

shutdown(wait=True) 相当于进程池的 pool.close()+pool.join() 操作

wait=True,等待池内所有任务执行完毕回收完资源后才继续,--------》默认

wait=False,立即返回,并不会等待池内的任务执行完毕

但不管wait参数为何值,整个程序都会等到所有任务执行完毕

Executor.add_done_callback(fn)

future都有 .add_done_callback(fn) 方法,这个方法只有一个参数,类型是可调用的对象,future运行结束后会调用指定的可调用对象。

fn接收一个future参数,通过obj.result(),获得执行后结果。

Executor.result()

.result()方法,在future运行结束后调用的话,返回可调用对象的结果,或者重新抛出执行可调用的对象时抛出的异常。

如果没有运行结束,concurrent会阻塞调用方直到有结果可返回。

concurrent.futures.as_completed()

使用concurrent.futures.as_completed函数,这个函数的参数是一个future列表 / future为key的字典,返回值是一个生成器,

在没有任务完成的时候,会阻塞,在有某个任务完成的时候,会yield这个任务future,就能执行for循环下面的语句,然后继续阻塞,循环到所有的任务结束。

从结果也可以看出,先完成的任务会先通知主线程。

Executor.map(func, * iterables, timeout=None)

Executor.map() 返回值是一个迭代器,迭代器的__next__方法调用各个future的result()方法,得到各个future的结果而不是future本身。

*iterables:可迭代对象,如列表等。每一次func执行,都会从iterables中取参数。

timeout:设置每次异步操作的超时时间

修改Executor.map调用,换成两个for循环,一个用于创建并排定future,另一个用于获取future的结果

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群991032883
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
def download_many():

    with futures.ThreadPoolExecutor(max_workers=3) as executor:

        to_do = []
        for cc in sorted(url_list):
            future = executor.submit(download_one,cc)
            to_do.append(future)

        result = []
        for future in futures.as_completed(to_do)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值