在现代编程中,并发是提高程序效率的关键技术之一,它允许程序同时执行多个任务,充分利用系统资源。

本文将深入探讨 Python 中的async/await机制,从并发编程基础讲起,逐步剖析其工作原理和实现方式。

1. 并发编程基础

计算机程序的执行方式主要有两种:顺序执行并发执行

顺序执行是按代码顺序逐条运行,而并发执行则允许同时运行多个任务。

并发又分为并发concurrency)和并行parallelism),并发是指多个任务同时进行,但不一定同时运行;并行则是多个任务同时运行,通常需要多核处理器支持。

假设有3个任务,每个任务有若干步骤,每个任务情况如下:

顺序执行的情况如下:

并发concurrency)执行的情况如下,三个任务交替执行,感觉像是同时在运行。

并行parallelism)执行的情况如下,三个任务同时运行。

不同的编程语言对并发编程的支持各有不同。

Python 通过 GIL(全局解释器锁)限制了多线程的并行能力,但提供了多种并发编程方式,如线程、多进程、事件循环等,这些方式各有优缺点,适用于不同的场景。

2. async/await 语法

Python 3.5开始,引入了一种新的异步编程语法async/await,用于简化异步操作的编写。

它基于生成器和事件循环,使得异步代码更加直观和易于理解。

其中,async关键字用于定义一个异步函数。

当一个函数被定义为async时,它会返回一个协程对象。

协程是一种特殊的函数,它可以在执行过程中暂停和恢复,非常适合处理 I/O 密集型任务。

比如:

async def fetch_data():
await asyncio.sleep(2) # 模拟异步操作
return "Data fetched"

调用async函数时,不会立即执行函数体,而是返回一个协程对象。要运行协程,需要将其提交到事件循环中。

await关键字用于暂停当前协程的执行,等待一个可等待对象(如协程、Future 或 Task)完成。

await后面的表达式必须是一个可等待对象,否则会抛出TypeError

比如:

async def main():
result = await fetch_data() # 暂停 main,直到 fetch_data 完成
print(result)

当遇到await时,当前协程会暂停执行,并将控制权交还给事件循环。

事件循环会继续执行其他任务,直到await的异步操作完成。

2.1. 执行流程

async/await的执行流程一般分为3步:

  1. 协程的启动:调用async函数会返回一个协程对象,要执行这个协程,需要将其提交给事件循环,比如通过asyncio.run()loop.run_until_complete()方法。
  2. 暂停与恢复:当协程遇到 await 时,它会暂停并将控制权交给事件循环。事件循环接着执行其他任务,直到 await 的操作完成,然后恢复该协程的执行。
  3. 异常处理async/await支持在协程中使用try/except捕获异常,这使得错误处理更加直观和方便。
async def risky_task():
raise ValueError("Something went wrong") async def main():
try:
await risky_task()
except ValueError as e:
print(f"Caught an exception: {e}")

2.2. async/await的优势

其实不用async/await的语法,也可以实现异步,Python引入这个语法的主要是因为可以带来一下的好处:

  1. 代码简洁易读async/await使得异步代码更加接近同步代码,避免了回调地狱和复杂的链式调用
  2. 错误处理方便: 使用try/except可以直接捕获协程中的异常,而无需在每个异步操作中处理错误
  3. 性能优化async/await基于事件循环和协程,避免了线程切换的开销,适合处理大量 I/O 密集型任务

2.3. 基于async/await的服务器实现

以下是使用async/awaitasyncio实现的 TCP Echo 服务器代码。

async/await之前的Python语法相比,代码更加简洁易读。

import asyncio

async def echo_handler(reader, writer):
addr = writer.get_extra_info("peername")
print(f"Connected from {addr}")
while True:
data = await reader.read(1024) # 非阻塞读取数据
if not data:
break
writer.write(data) # 非阻塞写入数据
await writer.drain() # 等待数据发送完成
writer.close()
print(f"Connection closed from {addr}") async def run_server():
server = await asyncio.start_server(echo_handler, "127.0.0.1", 8080)
async with server:
await server.serve_forever() if __name__ == "__main__":
asyncio.run(run_server())

3. asyncio 库

async/await只是Python语言层面的特性,而asyncioPython的标准异步编程库,提供了一套完整的工具和接口,用于构建异步应用程序。

asyncio的核心功能围绕事件循环展开,通过事件循环,asyncio能够高效地管理并发任务,实现 I/O 操作的异步执行。

它的主要功能和组件包括:

3.1. 事件循环(Event Loop)

事件循环asyncio的核心,它负责调度和管理异步任务。

事件循环的主要职责包括:

  1. 任务调度:事件循环会跟踪所有注册的任务,并根据任务的状态(如等待 I/O 操作或定时器到期)调度它们的执行。
  2. I/O 多路复用:通过底层的 I/O 多路复用机制(如selectepollkqueue),事件循环能够高效地处理多个并发的 I/O 操作。
  3. 异步任务的生命周期管理:事件循环负责启动、暂停、恢复和取消异步任务。

Python 中,可以通过asyncio.get_event_loop()获取当前的事件循环,或者使用asyncio.run()启动一个新的事件循环。

3.2. 协程(Coroutines)

协程是asyncio的基本执行单元,它通过asyncawait关键字定义。

协程可以暂停和恢复执行,非常适合处理 I/O 密集型任务。

以下是一个简单的协程示例:

async def fetch_data():
await asyncio.sleep(2) # 模拟异步 I/O 操作
return "Data fetched" async def main():
result = await fetch_data()
print(result) asyncio.run(main())

asyncio中,协程通过事件循环进行调度。

当遇到await时,当前协程会暂停执行,事件循环会继续处理其他任务,直到await的异步操作完成。

3.3. 任务(Tasks)

任务是协程的封装,它允许对协程进行更细粒度的控制,任务可以被取消、等待或加入到任务组中。

以下是一个使用任务的示例:

async def worker(name, delay):
await asyncio.sleep(delay)
print(f"Worker {name} completed") async def main():
task1 = asyncio.create_task(worker("A", 2))
task2 = asyncio.create_task(worker("B", 3))
await task1
await task2 asyncio.run(main())

asyncio中,任务是通过asyncio.create_task()创建的。任务可以被加入到任务组中,以便并行执行多个任务。

3.4. Future 对象

Future是一个表示异步操作结果的对象。

它通常用于低层次的异步编程,例如在回调函数中处理异步操作的结果。

Future对象可以通过set_result()set_exception()设置结果或异常。

async def main():
loop = asyncio.get_running_loop()
future = loop.create_future()
loop.call_soon(future.set_result, "Hello, Future!")
result = await future
print(result) asyncio.run(main())

asyncio中,Future对象通常用于与底层事件循环交互,而协程和任务则更常用于高层的异步编程。

3.5. 回调管理

asyncio提供了强大的回调管理功能,允许在特定事件发生时执行回调函数。

例如,可以通过loop.call_soon()loop.call_later()将回调函数加入到事件循环中。

async def main():
loop = asyncio.get_running_loop()
loop.call_soon(lambda: print("Callback executed immediately"))
loop.call_later(2, lambda: print("Callback executed after 2 seconds"))
await asyncio.sleep(3) # 等待足够的时间以触发回调 asyncio.run(main())

回调管理asyncio的一个重要特性,它允许开发者在事件循环中插入自定义的逻辑。

3.6. 优势与局限性

asyncio的优势非常明显:

  1. 高性能asyncio基于单线程事件循环,避免了线程切换的开销,适合处理大量并发的 I/O 密集型任务
  2. 简洁易读async/await语法使得异步代码更加接近同步代码,易于理解和维护
  3. 强大的功能asyncio提供了丰富的功能,包括任务调度、回调管理、异步网络通信等

不过,它的局限性也不能忽视:

  1. CPU密集型任务的限制:由于asyncio基于单线程事件循环,它不适合处理 CPU 密集型任务。对于这类任务,建议使用多进程或其他并发模型
  2. 兼容性问题asyncio的某些功能可能与传统的同步代码不兼容,需要开发者进行适当的适配
  3. 调试复杂性:虽然asyncio提供了强大的异步编程能力,但调试异步代码可能比调试同步代码更复杂

4. 总结

async/await模式是Python中一种高效的并发编程方式。

它结合了生成器和事件循环的优点,提供了简洁易读的代码。

然而,它也有缺点,例如对 CPU-bound 任务支持不足,除了async/awaitPython 还有其他并发编程模型,如多进程、线程池等。

此外,也介绍了asyncio库,它也在不断改进和扩展。

例如,Python 3.10 引入了asyncio.run()的改进版本,使得异步程序的启动更加简洁。

并且asyncio也在不断优化其性能和兼容性,以更好地支持现代异步应用的开发。

『Python底层原理』--异步机制(async/await)的更多相关文章

  1. 『Python基础-5』数字,运算,转换

    『Python基础-5』数字,运算,转换 目录 基本的数字类型 二进制,八进制,十六进制 数字类型间的转换 数字运算 1. 数字类型 Python 数字数据类型用于存储数学上的值,比如整数.浮点数.复 ...

  2. 『Python基础-1 』 编程语言Python的基础背景知识

    #『Python基础-1 』 编程语言Python的基础背景知识 目录: 1.编程语言 1.1 什么是编程语言 1.2 编程语言的种类 1.3 常见的编程语言 1.4 编译型语言和解释型语言的对比 2 ...

  3. 『Python基础-12』各种推导式(列表推导式、字典推导式、集合推导式)

    # 『Python基础-12』各种推导式(列表推导式.字典推导式.集合推导式) 推导式comprehensions(又称解析式),是Python的一种独有特性.推导式是可以从一个数据序列构建另一个新的 ...

  4. 『Python基础-11』集合 (set)

    # 『Python基础-11』集合 (set) 目录: 集合的基本知识 集合的创建 访问集合里的值 向集合set增加元素 移除集合中的元素 集合set的运算 1. 集合的基本知识 集合(set)是一个 ...

  5. 『Python基础-10』字典

    # 『Python基础-10』字典 目录: 1.字典基本概念 2.字典键(key)的特性 3.字典的创建 4-7.字典的增删改查 8.遍历字典 1. 字典的基本概念 字典一种key - value 的 ...

  6. 『Python基础-9』元祖 (tuple)

    『Python基础-9』元祖 (tuple) 目录: 元祖的基本概念 创建元祖 将列表转化为元组 查询元组 更新元组 删除元组 1. 元祖的基本概念 元祖可以理解为,不可变的列表 元祖使用小括号括起所 ...

  7. 『Python基础-8』列表

    『Python基础-8』列表 1. 列表的基本概念 列表让你能够在一个地方存储成组的信息,其中可以只包含几个 元素,也可以包含数百万个元素. 列表由一系列按特定顺序排列的元素组成.你可以创建包含字母表 ...

  8. 『Python基础-7』for循环 & while循环

    『Python基础-7』for循环 & while循环 目录: 循环语句 for循环 while循环 循环的控制语句: break,continue,pass for...else 和 whi ...

  9. 『Python基础-6』if语句, if-else语句

    # 『Python基础-6』if语句, if-else语句 目录: 条件测试 if语句 if-else语句 1. 条件测试 每条if语句的核心都是一个值为True或False的表达式,这种表达式被称为 ...

  10. 『Python基础-4』字符串

    # 『Python基础-4』字符串 目录 1.什么是字符串 2.修改字符串 2.1 修改字符串大小 2.2 合并(拼接)字符串 2.3 使用乘号'*'来实现字符串的叠加效果. 2.4 在字符串中添加空 ...

随机推荐

  1. 2024年1月Java项目开发指南16:用户自由选择字段查询、是否模糊查询

    我们希望用户可以自己控制是否要模糊查询 用户可以自由的选择字段去查询. 如上图,我在前端页面准备了 多选框:决定是否模糊查询.(True or False) 下拉选择框:决定要查询关键词的所属字段 输 ...

  2. 【转载】Spring Cloud Gateway排错、调试技巧总结

    http://www.imooc.com/article/290824 本文总结Spring Cloud Gateway的排错.调试技巧.欢迎留言补充! 第一式:Actuator监控端点 借助Actu ...

  3. P10952 聚会 题解

    题目链接 题目大意 对于一棵树,求出一个点对于给定的三个点(以下简称 $x$,$y$,$z$ 且可以重复)距离最短. 题解 对于点的距离,不难想到 LCA 处理.而对于本题,则有两种情况. 第一问 三 ...

  4. Qt编写地图综合应用33-雨量分布

    一.前言 雨量分布图是在区域地图基础上,针对区域中的每个最小单位区域比如县城点位不同颜色显示,最开始做这个封装的时候,并没有提供单独设置每个点颜色的接口,后面经过几个客户的强烈建议,咬咬牙把每个点都可 ...

  5. [转]springboot 监控 Actuator和Admin

    参考链接: 1.springboot 监控 Actuator和Admin 2.SpringBoot:Actuator监控中心+AdminUI界面管理

  6. 一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等

    1.引言 经历过稍有些规模的IM系统开发的同行们都有体会,要想实现大规模并发IM(比如亿级用户和数十亿日消息量这样的规模),在架构设计上需要一些额外的考虑,尤其是要解决用户高并发.服务高可用,架构和实 ...

  7. CDS标准视图:一次性账户的客户行项目 I_ONETIMEACCOUNTCUSTOMER

    视图名称:一次性账户的客户行项目 视图类型:基础 视图代码: 点击查看代码 @EndUserText.label: 'One-Time Account Data for Customer Items' ...

  8. [转载]「服务」WCF中NetNamedPipeBinding的应用实例

    「服务」WCF中NetNamedPipeBinding的应用实例 WCF中有很多种绑定,根据官方的说法,NetNamedPipeBinding是适用于同一台主机中不同进程之间的通信的. 今天终于实现了 ...

  9. XXL-JOB原理--定时任务框架简介

    一.完整介绍地址:官方介绍 https://www.xuxueli.com/xxl-job/#/?id=%E4%B8%80%E3%80%81%E7%AE%80%E4%BB%8B 二.最新版本架构图: ...

  10. w3cschool-MyBatis-Plus 插件

    https://www.w3cschool.cn/mybatis_plus/mybatis_plus-udwn3mgc.html MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具 ...