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

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

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

1. 并发编程基础

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

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

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

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

顺序执行的情况如下:

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

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

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

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

2. async/await 语法

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

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

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

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

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

比如:

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

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

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

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

比如:

python 复制代码
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捕获异常,这使得错误处理更加直观和方便。
python 复制代码
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语法相比,代码更加简洁易读。

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 密集型任务。

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

python 复制代码
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)

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

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

python 复制代码
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()设置结果或异常。

python 复制代码
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()将回调函数加入到事件循环中。

python 复制代码
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也在不断优化其性能和兼容性,以更好地支持现代异步应用的开发。

相关推荐
kfepiza3 分钟前
Python的循环和条件判断 笔记250303
开发语言·笔记·python
川石课堂软件测试1 小时前
涨薪技术|持续集成Git使用详解
开发语言·javascript·git·python·功能测试·ci/cd·单元测试
小小码农一只1 小时前
轻松部署 Stable Diffusion WebUI 并实现局域网共享访问:解决 Conda Python 版本不为 3.10.6 的难题
python·stable diffusion·conda
阿正的梦工坊1 小时前
解析 PyTorch 中的 torch.multinomial 函数
人工智能·pytorch·python
kcarly1 小时前
Web Snapshot 网页截图 模块代码详解
前端·python·网页截图
王有品2 小时前
python之爬虫入门实例
开发语言·爬虫·python
岱宗夫up2 小时前
【django初学者项目】
python·django·html
万山y2 小时前
curosr提示词推荐
python
麦麦大数据2 小时前
vue+neo4j 四大名著知识图谱问答系统
vue.js·人工智能·python·django·问答系统·知识图谱·neo4j
程序员三藏2 小时前
Jmeter简单的压力测试
自动化测试·软件测试·python·测试工具·jmeter·测试用例·压力测试