python并发学习——asyncio包解析

asyncio中Future、Task和协程之间的关系

  • Future是调度执行某物的结果
  • 在asyncio中,有很多函数能够接收一个协程,排定它的运行时间,然后返回一个asyncio.Task对象(实例)——也是asyncio.Future类的实例,因为Task是Future的子类,用于包装和驱动协程。

asyncio为什么与协程一起使用

协程中的yield关键字,不管数据如何流动,都是一种流程控制工具,使用它可以实现协作式多任务;协程可以把控制器让步给中心调度程序,从而激活其他的协程。

协程中的yield from语句能够打开双向通道,把最外层的事件循环和内部的真正执行耗时操作的I/O函数连接起来,这样两者可以直接发送和产出值,还可以直接传入异常,而不用在中间的协程中添加大量处理异常的样板代码。有了这个结构,协程可以通过以前不可能的方式委派职责。当最内层的I/O操作运行结束并返回时,解释器会抛出StopIteration异常,并把返回值附加到异常对象,yield from能够捕获StopIteration异常并获取返回值,此时恢复中间层的协程的运行。这就实现了事件循环、中间层协程和内层的耗时的子生成器之间的非阻塞型调度。

asyncio包中能够创建Task对象的函数

  • asyncio.async(coro_or_funture, * , loop=None)

    这个函数统一了协程和future:第一个参数可以是二者中的任意一个。如果是Future或Task对象,那就原封不动的返回。如果是协程,那么asyn函数会调用loop.create_task(…)方法创建Task对象。loop=关键字参数是可选的,用于传入事件循环;如果没有传入,那么async函数会调用asyncio.get_event_loop()函数获取循环对象。

  • BaseEventLoop.create_task(coro)

    这个方法排定协程的执行时间,返回一个asyncio.Task对象。如果在自定义的BaseEventLoop子类上调用,返回的对象可能是外部库(如Tornado)中与Task类兼容的某个类的实例。

  • asyncio.wait(…)

    asyncio.wait(…)协程的参数是一个由future或者协程构成的可迭代对象;wait会分别把各个协程装进一个Task对象。最终的结果是,wait处理的所有对象都通过某种方式变成Future类的实例。

asyncio驱动的事件循环为什么不会阻塞

事件循环loop调用方法loop.run_until_complete(…)驱动传入的参数,它的参数是一个future或者协程。如果是协程,run_until_complete方法和wait函数一样,把协程包装进一个Task对象中。协程、future和任务(Task)都由yield from驱动,这正是run_until_complete方法对其参数所做的事情。

yield from foo句法能够防止阻塞,是因为当前协程(即包括yield from代码的委派生成器)暂停后,控制权回到事件循环手中,再去驱动其它协程;foo future或协程运行结束后,把结果返回到暂停的协程(yield from自动捕获StopIteration),将其恢复。

这样,最外层的事件循环始终可以不被阻塞,并发地调度所有协程向前推进。

阻塞型I/O和GIL

  • 阻塞型函数:Ryan Dahl(Node.js的发明者)在介绍他的项目背后的哲学时说:“我们处理I/O的方式彻底错了。”他把执行硬盘或者网络I/O操作的函数定义为阻塞型函数,主张不能像对待非阻塞型函数那样对待阻塞型函数。

cpython解释器本身就不是线程安全的,因此有全局解释器锁(GIL),一次只会允许使用一个线程执行python字节码。一次,一个python进程通常不能同时使用多个CPU核心。

然而,标准库中所有执行阻塞型I/O操作的函数,在等待操作系统返回结果时都会释放GIL。这意味着在python语言这个层次上可以使用多线程,而I/O密集型python程序能从中受益:一个python线程等待网络响应时,阻塞型I/O函数会释放GIL,在运行一个线程。

猜你喜欢

转载自blog.csdn.net/jasonzhoujx/article/details/81560443