前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python:从头创建 Asyncio (1)

Python:从头创建 Asyncio (1)

作者头像
数据科学工厂
发布2024-06-18 14:17:39
830
发布2024-06-18 14:17:39
举报

引言

现在,asyncio 已成为 Python 社区中的热门话题,并且名副其实——它提供了一种非常出色的处理 I/O 密集型程序的方法!在我探索 asyncio 的过程中,我起初并不太明白它的工作原理。但随着深入学习,我意识到 asyncio 实际上是在 Python 生成器的基础上增加了一层非常便利的封装。

本文[1]中,我将展示如何仅用 Python 生成器来构建一个 asyncio 的简化模型。接着,我会演示如何利用 await 魔法方法,将示例代码改写为使用 async 和 await 关键字。最终,我会将我的简化版本替换为官方的 asyncio 库。通过这个过程,我相信你将对 asyncio 的神奇之处有一个更深入的理解。

生成器

如果您已经熟悉生成器,请跳过这一部分,但如果您不熟悉,那么 asyncio 就是基于它构建的,因此了解它们的工作原理非常重要。

首先,生成器之所以存在,是因为它们可以让你的代码更加内存高效。想象一下,如果您有以下循环:

代码语言:javascript
复制
for i in range(100_000_000):
    print(i)

如果 range 函数不是以生成器的形式存在,而是返回一个列表让你去遍历,那么类似上面例子的代码在内存使用上将非常浪费,因为你需要创建一个包含高达 1 亿个元素的列表。但是,由于 range 在 Python 3 或更高版本中是一个生成器,你只需在需要时逐个生成数字,而不必将整个序列一次性加载到内存中。

创建生成器有多种方法,但本文将重点介绍生成器函数。生成器函数的声明与其他函数无异,但它使用 yield 语句来逐个返回数据。这个 yield 语句将普通函数转变为一个可以按需暂停和恢复执行状态的生成器,这通过调用 next(iterator) 来实现。

例如,下面是一个生成器函数的示例:

代码语言:javascript
复制
def generator():
   yield 'hello'
   yield 'world'

iterator = generator()

当您调用生成器时,它不会像 Python 通常那样运行函数内部的代码,而是会看到yield 关键字,因此返回一个生成器对象。一旦我们有了生成器对象,我们就可以调用 next(iterator),它将运行函数的代码,直到第一个/下一个yield语句:

代码语言:javascript
复制
print(next(iterator))  # Output: hello
print(next(iterator))  # Output: world

如果我们尝试再次调用 next(iterator),生成器将引发 StopIteration 异常,因为生成器函数中不再有yield 语句。 Python 生成器的另一个很酷的功能是yield from,它允许生成器调用子生成器或可迭代对象,使您能够创建生成器链!

代码语言:javascript
复制
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 后,管理器会重新获得控制权,并继续执行循环中的下一个任务。

代码语言:javascript
复制
def task1():
   while True:
       print('Task 1')
       yield

def task2():
   while True:
       print('Task 2')
       yield

event_loop = [task1(), task2()]

while True:
   for task in event_loop:
       next(task)

随后,该代码的输出将如下所示,并且将永远持续下去,因为由于 while True 循环,两个生成器函数都永远不会完成。

代码语言:javascript
复制
Task 1
Task 2
Task 1
Task 2
…
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-06-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 冷冻工厂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • 生成器
  • 事件循环
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档