Manjusaka

Manjusaka

asyncio 笔记

来源 annotated-py-asyncio

阅读补充:#

1. 基本概念:#

1.1 协程:#

  • “协程 是为非抢占式多任务产生子程序的计算机程序组件,协程允许不同入口点在不同位置暂停或开始执行程序”。
  • 从技术的角度来说,“协程就是你可以暂停执行的函数”。
  • 如果你把它理解成 “就像生成器一样”,那么你就想对了。

1.2 事件循环:#

  • 事件循环 “是一种等待程序分配事件或消息的编程架构”。
  • 基本上来说事件循环就是,“当 A 发生时,执行 B”。
  • 或许最简单的例子来解释这一概念就是用每个浏览器中都存在的 JavaScript 事件循环。
  • 当你点击了某个东西(“当 A 发生时”),这一点击动作会发送给 JavaScript 的事件循环,并检查是否存在注册过的 onclick 回调来处理这一点击(“执行 B”)。
  • 只要有注册过的回调函数就会伴随点击动作的细节信息被执行。
  • 事件循环被认为是一种循环是因为它不停地收集事件并通过循环来发如何应对这些事件。

1.3 Python 的事件循环:#

  • 对 Python 来说,用来提供事件循环的 asyncio 被加入标准库中。
  • asyncio 重点解决网络服务中的问题,事件循环在这里将来自套接字(socket)的 I/O 已经准备好读和 / 或写作为 “当 A 发生时”(通过 selectors 模块)。
  • 除了 GUI 和 I/O,事件循环也经常用于在别的线程或子进程中执行代码,并将事件循环作为调节机制(例如,合作式多任务)。
  • 如果你恰好理解 Python 的 GIL,事件循环对于需要释放 GIL 的地方很有用。
  • 事件循环提供一种循环机制,让你可以 “在 A 发生时,执行 B”。
  • 基本上来说事件循环就是监听当有什么发生时,同时事件循环也关心这件事并执行相应的代码。
  • Python 3.4 以后通过标准库 asyncio 获得了事件循环的特性。

1.4 async, await:#

  • 将 async/await 看做异步编程的 API
  • 基本上 async 和 await 产生神奇的生成器,我们称之为协程,
  • 同时需要一些额外的支持例如 awaitable 对象以及将普通生成器转化为协程。
  • 所有这些加到一起来支持并发,这样才使得 Python 更好地支持异步编程。
  • 相比类似功能的线程,这是一个更妙也更简单的方法。

在 Python 3.4 中,用于异步编程并被标记为协程的函数看起来是这样的:


    # This also works in Python 3.5.
    @asyncio.coroutine
    def py34_coro():
        yield from stuff()

Python 3.5 添加了 types.coroutine 修饰器,也可以像 asyncio.coroutine 一样将生成器标记为协程。
你可以用 async def 来定义一个协程函数,虽然这个函数不能包含任何形式的 yield 语句;只有 return 和 await 可以从协程中返回值。

    async def py35_coro():
        await stuff()

你将发现不仅仅是 async,Python 3.5 还引入 await 表达式(只能用于 async def 中)。
虽然 await 的使用和 yield from 很像,但 await 可以接受的对象却是不同的。
await 当然可以接受协程,因为协程的概念是所有这一切的基础。
但是当你使用 await 时,其接受的对象必须是 awaitable 对象:必须是定义了__await__() 方法且这一方法必须返回一个不是协程的迭代器。
协程本身也被认为是 awaitable 对象(这也是 collections.abc.Coroutine 继承 collections.abc.Awaitable 的原因)。
这一定义遵循 Python 将大部分语法结构在底层转化成方法调用的传统,就像 a + b 实际上是 a.add(b) 或者 b.radd(a)。

为什么基于 async 的协程和基于生成器的协程会在对应的暂停表达式上面有所不同?
主要原因是出于最优化 Python 性能的考虑,确保你不会将刚好有同样 API 的不同对象混为一谈。
由于生成器默认实现协程的 API,因此很有可能在你希望用协程的时候错用了一个生成器。
而由于并不是所有的生成器都可以用在基于协程的控制流中,你需要避免错误地使用生成器。

用 async def 可以定义得到协程。
定义协程的另一种方式是通过 types.coroutine 修饰器
-- 从技术实现的角度来说就是添加了 CO_ITERABLE_COROUTINE 标记
-- 或者是 collections.abc.Coroutine 的子类。
你只能通过基于生成器的定义来实现协程的暂停。

awaitable 对象要么是一个协程要么是一个定义了__await__() 方法的对象
-- 也就是 collections.abc.Awaitable
-- 且__await__() 必须返回一个不是协程的迭代器。

await 表达式基本上与 yield from 相同但只能接受 awaitable 对象(普通迭代器不行)。
async 定义的函数要么包含 return 语句
-- 包括所有 Python 函数缺省的 return None
-- 和 / 或者 await 表达式(yield 表达式不行)。
async 函数的限制确保你不会将基于生成器的协程与普通的生成器混合使用,因为对这两种生成器的期望是非常不同的。

1.5 关于 python 协程 和 golang 的对比讨论:#

  • From Python to Go and Back Again
    • PPT
    • 关于此 PPT 的观点: go 比 pypy 性能高不了多少,但是复杂度和调试难度增加很高
    • 结尾鼓吹 rust.
  • 异步库参考:
    • hyper
    • curio
      • 将 asyncio 看作是一个利用 async/await API 进行异步编程的框架
      • David 将 async/await 看作是异步编程的 API 创建了 curio 项目来实现他自己的事件循环。
      • 允许像 curio 一样的项目不仅可以在较低层面上拥有不同的操作方式
      • (例如 asyncio 利用 future 对象作为与事件循环交流的 API,而 curio 用的是元组)

2. 源码模块:#

2.1 (futures.py)[./futures.py]#

2.1.1 参考:#

2.1.2 生成器(Generator)VS 迭代器(iterator):#

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。