Python中的异步编程
如果你对Python中的异步编程还不熟悉,这个页面就是为你准备的。
在LlamaIndex中,许多操作和函数支持异步执行。这使您能够同时运行多个操作而不会阻塞主线程,这在许多情况下有助于提高整体吞吐量和性能。
以下是一些您应该理解的关键概念:
1. asyncio 基础asyncio
Section titled “1. The Basics of asyncio”-
事件循环: 事件循环负责调度和执行异步操作。它持续检查并执行任务(协程)。所有异步操作都通过此循环运行,且每个线程只能有一个事件循环。
-
asyncio.run()asyncio.run(): 该函数是运行异步程序的入口点。它会创建并管理事件循环,并在完成后进行清理。请注意,该函数设计为每个线程仅调用一次。某些框架(如FastAPI)会自动运行事件循环,而其他框架则需要您自行运行。 -
异步 + Python 笔记本: Python 笔记本是一个特殊情况,事件循环已在运行。这意味着您无需自行调用
asyncio.run(),可以直接调用并等待异步函数。
2. 异步函数与 awaitawait
Section titled “2. Async Functions and await”-
定义异步函数: 使用
async def语法来定义异步函数(协程)。调用异步函数时不会立即执行,而是返回一个需要被调度和运行的协程对象。 -
使用
await: 在异步函数内部,await用于暂停该函数的执行,直到被等待的任务完成。当您编写await some_fn()时,该函数将控制权交还给事件循环,以便可以调度和运行其他任务。一次只有一个异步函数在执行,它们通过使用await让出控制权来协作。
-
协作式并发: 虽然您可以调度多个异步任务,但一次只能运行一个任务。这与真正的并行性不同,在真正的并行性中多个任务同时运行。当任务遇到
await时,它会暂停执行以便其他任务可以运行。这使得异步程序非常适合需要频繁等待的I/O密集型任务,例如调用LLM和其他服务的API。 -
非真正并行: Asyncio 支持并发但不会并行运行任务。对于需要并行执行的 CPU 密集型任务,请考虑使用线程或多进程。LlamaIndex 在大多数情况下通常避免使用多进程,并将其留给用户自行实现,因为要以安全高效的方式实现可能较为复杂。
-
asyncio.to_thread(): 有时您需要在异步程序中运行同步(阻塞)代码而不冻结程序。asyncio.to_thread()将阻塞代码转移到单独的线程中,允许事件循环继续处理其他任务。请谨慎使用,因为它会增加一些开销,并可能使调试更具挑战性。 -
替代方案:执行器: 您可能还会遇到使用
loop.run_in_executor()来处理阻塞函数的情况。
以下是一个示例,展示如何使用 asyncio 编写和运行异步函数:
import asyncio
async def fetch_data(delay): print(f"Started fetching data with {delay}s delay")
# Simulates I/O-bound work, such as network operation await asyncio.sleep(delay)
print("Finished fetching data") return f"Data after {delay}s"
async def main(): print("Starting main")
# Schedule two tasks concurrently task1 = asyncio.create_task(fetch_data(2)) task2 = asyncio.create_task(fetch_data(3))
# Wait until both tasks complete result1, result2 = await asyncio.gather(task1, task2)
print(result1) print(result2) print("Main complete")
if __name__ == "__main__": asyncio.run(main())