【Python基础】Python异步并发(协程、线程)
1、协程、线程、进程
**协程**是单线程内的并发机制,协程是运行在线程上的轻量级任务。
由事件循环(EventLoop)在应用层进行调度。多个协程任务在同一个线程中交替推进,CPU 不一定同时执行多个任务,而是通过不断切换让 I/O 等待时间被充分利用——这种模式称为并发(Concurrency)。例如,当创建多个协程任务后,事件循环会管理它们的执行顺序:
a = asyncio.create_task(A)
b = asyncio.create_task(B)
results = await asyncio.gather(a, b)**线程**则是操作系统调度的基本单位,是进程里面的执行单位,多线程共享堆内存资源,
多线程可以实现并行(Parallelism)——即多个线程真正同时运行在不同的 CPU 核心上(受限于 Python 的 GIL,对于 CPU 密集型任务可能无法充分利用多核,但 I/O 密集型任务仍能通过线程切换获得并发效果)。
二者的关键区别在于阻塞时的处理方式:
- 线程中的任务一旦阻塞(如等待网络响应),该线程会挂起等待,无法执行其他任务,除非使用其他线程。
- 协程中的任务如果遇到阻塞(如
await等待 I/O),它会主动让出控制权,事件循环会立即调度另一个就绪的协程继续执行,从而避免线程空等。
**进程**是一个运行的程序实例,拥有独立的内存空间、资源,不同进程默认隔离。
示例场景:同时进行 BM25 检索和向量检索。
- 多线程:两个线程分别执行两种检索,各自阻塞等待结果返回,两个线程同时处于等待状态。
- 协程:在同一个线程中,两个协程交替执行,当第一个协程发起检索并
await等待时,事件循环切换到第二个协程执行,最大限度地利用等待时间。
IO密集型 → 优先考虑协程
阻塞库/简单并发 → 线程
CPU密集型 → 多进程
2、什么是异步并发
在 Python 中,异步编程主要通过 asyncio 库实现。asyncio 的底层基于事件循环(EventLoop),必须要有EventLoop才能执行异步并发,它负责调度和执行协程任务。
通常我们通过 asyncio.run(main()) 来启动一个事件循环并运行入口协程。而在 Web 框架 FastAPI 中,通常使用 uvicorn 等 ASGI 服务器,它会自动创建并管理事件循环,无需手动调用 asyncio.run():
app = FastAPI()
# uvicorn 启动时会自动建立事件循环
uvicorn.run(app, host="0.0.0.0", port=8000)定义一个异步函数使用 async def 关键字,内部可以 await 其他异步操作。创建任务并执行的方式包括:
asyncio.create_task(coro):将协程包装为任务,并立即加入事件循环调度。- 多个任务可通过
asyncio.gather()统一等待所有结果。
3、asyncio 使用
① 单个异步任务:await
适用场景:只执行一个异步操作,顺序等待其完成。
async def A():
return "A result"
result = await A()② 多任务并发:asyncio.gather()(最常用)
适用场景:启动多个独立任务,并同时等待所有任务完成,返回结果列表(或按顺序解包)。
async def A():
return "A"
async def B():
return "B"
result_a, result_b = await asyncio.gather(
A(),
B()
)gather 会并发执行传入的所有协程,并保证结果顺序与传入顺序一致。
③ 手动创建任务:asyncio.create_task()
适用场景:需要提前启动任务,在等待结果之前可以插入其他操作(如记录日志、准备数据等),之后再按需 await 获取结果。
task_a = asyncio.create_task(A())
task_b = asyncio.create_task(B())
# 此处可执行其他非阻塞操作
print("任务已创建,可以做其他事情...")
result_a = await task_a
result_b = await task_b这种方式与 gather 本质相似,但提供了更灵活的调度时机控制。