『Python底层原理』--异步机制(async/await)

打印 上一主题 下一主题

主题 1768|帖子 1768|积分 5304

在现代编程中,并发是进步程序效率的关键技术之一,它答应程序同时执行多个使命,充分利用系统资源。
本文将深入探究 Python 中的async/await机制,从并发编程底子讲起,逐步剖析其工作原理和实现方式。
1. 并发编程底子

计算机程序的执行方式主要有两种:顺序执行并发执行
顺序执行是按代码顺序逐条运行,而并发执行则答应同时运行多个使命。
并发又分为并发(concurrency)和并行(parallelism),并发是指多个使命同时举行,但不一定同时运行;并行则是多个使命同时运行,通常必要多核处理器支持。
假设有3个使命,每个使命有若干步调,每个使命情况如下:

顺序执行的情况如下:

并发(concurrency)执行的情况如下,三个使命瓜代执行,感觉像是同时在运行。

并行(parallelism)执行的情况如下,三个使命同时运行。

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

从Python 3.5开始,引入了一种新的异步编程语法async/await,用于简化异步操作的编写。
它基于生成器和事件循环,使得异步代码更加直观和易于理解。
其中,async关键字用于界说一个异步函数。
当一个函数被界说为async时,它会返回一个协程对象。
协程是一种特殊的函数,它可以在执行过程中暂停和规复,非常得当处理 I/O 密集型使命。
比如:
  1. async def fetch_data():
  2.     await asyncio.sleep(2)  # 模拟异步操作
  3.     return "Data fetched"
复制代码
调用async函数时,不会立即执行函数体,而是返回一个协程对象。要运行协程,必要将其提交到事件循环中。
await关键字用于暂停当前协程的执行,等待一个可等待对象(如协程、Future 或 Task)完成。
await后面的表达式必须是一个可等待对象,否则会抛出TypeError。
比如:
  1. async def main():
  2.     result = await fetch_data()  # 暂停 main,直到 fetch_data 完成
  3.     print(result)
复制代码
当遇到await时,当前协程会暂停执行,并将控制权交还给事件循环。
事件循环会继续执行其他使命,直到await的异步操作完成。
2.1. 执行流程

async/await的执行流程一样平常分为3步:

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

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

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

以下是使用async/await和asyncio实现的 TCP Echo 服务器代码。
与async/await之前的Python语法相比,代码更加简洁易读。
  1. import asyncio
  2. async def echo_handler(reader, writer):
  3.     addr = writer.get_extra_info("peername")
  4.     print(f"Connected from {addr}")
  5.     while True:
  6.         data = await reader.read(1024)  # 非阻塞读取数据
  7.         if not data:
  8.             break
  9.         writer.write(data)  # 非阻塞写入数据
  10.         await writer.drain()  # 等待数据发送完成
  11.     writer.close()
  12.     print(f"Connection closed from {addr}")
  13. async def run_server():
  14.     server = await asyncio.start_server(echo_handler, "127.0.0.1", 8080)
  15.     async with server:
  16.         await server.serve_forever()
  17. if __name__ == "__main__":
  18.     asyncio.run(run_server())
复制代码
3. asyncio 库

async/await只是Python语言层面的特性,而asyncio是Python的标准异步编程库,提供了一套完整的工具和接口,用于构建异步应用程序。
asyncio的核心功能围绕事件循环睁开,通过事件循环,asyncio能够高效地管理并发使命,实现 I/O 操作的异步执行。
它的主要功能和组件包括:
3.1. 事件循环(Event Loop)

事件循环是asyncio的核心,它负责调度和管理异步使命。
事件循环的主要职责包括:

  • 使命调度:事件循环会跟踪所有注册的使命,并根据使命的状态(如等待 I/O 操作或定时器到期)调度它们的执行。
  • I/O 多路复用:通过底层的 I/O 多路复用机制(如select、epoll或kqueue),事件循环能够高效地处理多个并发的 I/O 操作。
  • 异步使命的生命周期管理:事件循环负责启动、暂停、规复和取消异步使命。
在 Python 中,可以通过asyncio.get_event_loop()获取当前的事件循环,大概使用asyncio.run()启动一个新的事件循环。
3.2. 协程(Coroutines)

协程是asyncio的根本执行单元,它通过async和await关键字界说。
协程可以暂停和规复执行,非常得当处理 I/O 密集型使命。
以下是一个简朴的协程示例:
  1. async def fetch_data():
  2.     await asyncio.sleep(2)  # 模拟异步 I/O 操作
  3.     return "Data fetched"
  4. async def main():
  5.     result = await fetch_data()
  6.     print(result)
  7. asyncio.run(main())
复制代码
在asyncio中,协程通过事件循环举行调度。
当遇到await时,当前协程会暂停执行,事件循环会继续处理其他使命,直到await的异步操作完成。
3.3. 使命(Tasks)

使命是协程的封装,它答应对协程举行更细粒度的控制,使命可以被取消、等待或加入到使命组中。
以下是一个使用使命的示例:
  1. async def worker(name, delay):
  2.     await asyncio.sleep(delay)
  3.     print(f"Worker {name} completed")
  4. async def main():
  5.     task1 = asyncio.create_task(worker("A", 2))
  6.     task2 = asyncio.create_task(worker("B", 3))
  7.     await task1
  8.     await task2
  9. asyncio.run(main())
复制代码
在asyncio中,使命是通过asyncio.create_task()创建的。使命可以被加入到使命组中,以便并行执行多个使命。
3.4. Future 对象

Future是一个体现异步操作结果的对象。
它通常用于低条理的异步编程,例如在回调函数中处理异步操作的结果。
Future对象可以通过set_result()或set_exception()设置结果或异常。
  1. async def main():
  2.     loop = asyncio.get_running_loop()
  3.     future = loop.create_future()
  4.     loop.call_soon(future.set_result, "Hello, Future!")
  5.     result = await future
  6.     print(result)
  7. asyncio.run(main())
复制代码
在asyncio中,Future对象通常用于与底层事件循环交互,而协程和使命则更常用于高层的异步编程。
3.5. 回调管理

asyncio提供了强盛的回调管理功能,答应在特定事件发生时执行回调函数。
例如,可以通过loop.call_soon()或loop.call_later()将回调函数加入到事件循环中。
  1. async def main():
  2.     loop = asyncio.get_running_loop()
  3.     loop.call_soon(lambda: print("Callback executed immediately"))
  4.     loop.call_later(2, lambda: print("Callback executed after 2 seconds"))
  5.     await asyncio.sleep(3)  # 等待足够的时间以触发回调
  6. asyncio.run(main())
复制代码
回调管理是asyncio的一个重要特性,它答应开发者在事件循环中插入自界说的逻辑。
3.6. 优势与范围性

asyncio的优势非常明显:

  • 高性能:asyncio基于单线程事件循环,避免了线程切换的开销,得当处理大量并发的 I/O 密集型使命
  • 简洁易读:async/await语法使得异步代码更加靠近同步代码,易于理解和维护
  • 强盛的功能:asyncio提供了丰富的功能,包括使命调度、回调管理、异步网络通信等
不过,它的范围性也不能忽视:

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

async/await模式是Python中一种高效的并发编程方式。
它结合了生成器和事件循环的优点,提供了简洁易读的代码。
然而,它也有缺点,例如对 CPU-bound 使命支持不足,除了async/await,Python 还有其他并发编程模子,如多进程、线程池等。
此外,也介绍了asyncio库,它也在不断改进和扩展。
例如,Python 3.10 引入了asyncio.run()的改进版本,使得异步程序的启动更加简洁。
而且asyncio也在不断优化其性能和兼容性,以更好地支持现代异步应用的开发

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

温锦文欧普厨电及净水器总代理

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表