Python:从头创建 Asyncio (1)
## 引言现在,asyncio 已成为 Python 社区中的热门话题,并且名副其实——它提供了一种非常出色的处理 I/O 密集型程序的方法!在我探索 asyncio 的过程中,我起初并不太明白它的工作原理。但随着深入学习,我意识到 asyncio 实际上是在 Python 生成器的基础上增加了一层非常便利的封装。
[本文](https://jacobpadilla.com/articles/recreating-asyncio "Source")中,我将展示如何仅用 Python 生成器来构建一个 asyncio 的简化模型。接着,我会演示如何利用 **await** 魔法方法,将示例代码改写为使用 async 和 await 关键字。最终,我会将我的简化版本替换为官方的 asyncio 库。通过这个过程,我相信你将对 asyncio 的神奇之处有一个更深入的理解。
## 生成器
如果您已经熟悉生成器,请跳过这一部分,但如果您不熟悉,那么 asyncio 就是基于它构建的,因此了解它们的工作原理非常重要。
首先,生成器之所以存在,是因为它们可以让你的代码更加内存高效。想象一下,如果您有以下循环:
```python
for i in range(100_000_000):
print(i)
```
如果 range 函数不是以生成器的形式存在,而是返回一个列表让你去遍历,那么类似上面例子的代码在内存使用上将非常浪费,因为你需要创建一个包含高达 1 亿个元素的列表。但是,由于 range 在 Python 3 或更高版本中是一个生成器,你只需在需要时逐个生成数字,而不必将整个序列一次性加载到内存中。
创建生成器有多种方法,但本文将重点介绍生成器函数。生成器函数的声明与其他函数无异,但它使用 yield 语句来逐个返回数据。这个 yield 语句将普通函数转变为一个可以按需暂停和恢复执行状态的生成器,这通过调用 next(iterator) 来实现。
例如,下面是一个生成器函数的示例:
```python
def generator():
yield 'hello'
yield 'world'
iterator = generator()
```
当您调用生成器时,它不会像 Python 通常那样运行函数内部的代码,而是会看到yield 关键字,因此返回一个生成器对象。一旦我们有了生成器对象,我们就可以调用 next(iterator),它将运行函数的代码,直到第一个/下一个yield语句:
```python
print(next(iterator))# Output: hello
print(next(iterator))# Output: world
```
如果我们尝试再次调用 next(iterator),生成器将引发 StopIteration 异常,因为生成器函数中不再有yield 语句。
Python 生成器的另一个很酷的功能是yield from,它允许生成器调用子生成器或可迭代对象,使您能够创建生成器链!
```python
def generator():
yield 'hello'
def another_generator():
yield from generator()
iterable = another_generator()
print(next(iterable))# Output: hello
```
生成器的功能远不止我提到的这些,例如生成器推导式,它与列表推导式类似,但使用的是圆括号而非方括号,还有通过 iterator.send(value) 方法向生成器传递数据的功能。不过,对于本文而言,最关键的是理解生成器能够让函数在执行过程中暂停和恢复,同时保持其内部状态。
## 事件循环
事件循环是 asyncio 的心脏,负责驱动和管理所有当前任务的执行,我们将首先用生成器来模拟它。虽然 asyncio 的事件循环是用 C 语言实现的,但我们可以将其想象成一个容器,里面存放着所有活跃的任务。目前,我们把这些任务看作是生成器对象。事件循环管理器会依次遍历容器中的任务,并通过调用 next(task) 函数来执行它们。当任务执行到 I/O 操作,比如等待(sleep)时,它会使用 yield 关键字来挂起当前的执行流程,并将控制权交还给事件循环,后者随后会转向执行队列中的下一个任务。
举个例子,我们有两个任务,它们首先打印出自己的任务编号,然后执行 yield 操作,这会导致它们的执行被挂起。因为事件循环管理器负责调用 next() 函数,所以在任务执行 yield 后,管理器会重新获得控制权,并继续执行循环中的下一个任务。
```python
def task1():
while True:
print('Task 1')
yield
def task2():
while True:
print('Task 2')
yield
event_loop =
while True:
for task in event_loop:
next(task)
```
随后,该代码的输出将如下所示,并且将永远持续下去,因为由于 while True 循环,两个生成器函数都永远不会完成。
```txt
Task 1
Task 2
Task 1
Task 2
…
```
## Python Books
![](https://files.mdnice.com/user/35537/ea5329ad-1a48-4e68-a5e5-32db24acb700.jpg)
> **Python 电子书 自取链接:https://pan.quark.cn/s/b5b00664b0c0**
>
页:
[1]