协程
| 代码 | 概念 |
|---|---|
async def gen_cases |
定义协程函数 |
gen_cases(r) 不加 await 时 |
只是协程对象,不执行 |
await client...create |
在结果返回前挂起,交还控制权给事件循环(EventLoop) |
asyncio.gather(*[...]) |
协程任务采集后一起交给事件循环 |
asyncio.run(main()) |
事件循环入口 |
async with sem |
Semaphore限流,控制并发数 |
事件循环与OS的交互: 当 Socket 就绪或 I/O 完成时,OS 唤醒正在等待的事件循环;事件循环执行 I/O 回调、完成 Future,再调度 Task 恢复协程。
import asyncio
from openai import AsyncOpenAI
client = AsyncOpenAI()
# 全局一份信号量:最多 10 个请求同时在飞
sem = asyncio.Semaphore(10)
async def gen_cases(requirement):
# 进门前先领令牌,领不到就挂起排队(交还事件循环)
async with sem:
resp = await client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"为这个需求生成测试用例:{requirement}"}],
)
return resp.choices[0].message.content
async def main():
reqs = ["登录功能", "支付功能", "注册功能", ...] # 假设 1000 个
# 1. 列表推导:把每个 req 变成协程(此时都还没跑)
# 2. *:拆开成一个个协程
# 3. gather:一起交给事件循环
# 4. await:main 在此等全部跑完
results = await asyncio.gather(*[gen_cases(r) for r in reqs])
for r in results:
print(r)
# 整个异步程序的入口:建事件循环 → 跑 main → 关闭
asyncio.run(main())
🚁 uvloop
asyncio事件循环的替代方案,基于uvloop的asyncio的速度几乎接近了Go程序(前提是单核+IO密集型任务)的速度。
基于C语言的底层库: libuv(跨平台统一 epoll/kqueue/IOCP),只需替换事件循环,解决异步IO的效率。
为什么 C 库比 Python 快
-
解释 vs 编译
Python 代码要先编译成字节码,再由解释器逐条读、逐条执行。每执行一条字节码,都有一次“分发开销”(判断这是哪条指令、然后跳到对应处理)。
C 代码编译成机器码,CPU 直接执行指令,没有“逐条读”这一层。
-
动态类型检查
Python 里 total += i,解释器每一轮都要先查 total 是什么类型、i 是什么类型,才能决定怎么加。
C 里 int total,类型编译期就定死,直接整数加法,不查。
-
对象模型开销
Python 的 int 是对象(PyObject),有引用计数、有结构体头。每次运算都要创建新对象、改引用计数、释放旧对象。
C 的 int 就是内存里 4/8 个字节,算完就完事。
import asyncio
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
# 编写异步代码,与之前步骤一致
asyncio.run()
🚁 异步编程中遇到CPU密集任务
思路: 把CPU密集/同步阻塞任务,放到事件循环外,避免阻塞主线程。
cpu_bound() 保持普通同步函数,用 loop.run_in_executor(process_pool, cpu_bound, text),再 await 它
import asyncio
import os
import time
from concurrent.futures import ProcessPoolExecutor
def cpu_bound(n: int) -> int:
"""模拟 CPU 密集任务:纯 Python 计算,适合放进程池。"""
total = 0
for i in range(n):
total += i * i
return total
async def heartbeat(stop: asyncio.Event) -> None:
"""心跳协程:如果事件循环没被阻塞,它会持续打印。"""
i = 0
while not stop.is_set():
print(f"[heartbeat {i}] {time.strftime('%X')}")
i += 1
await asyncio.sleep(0.3)
async def main() -> None:
loop = asyncio.get_running_loop()
stop = asyncio.Event()
hb_task = asyncio.create_task(heartbeat(stop))
# 关键:CPU 密集用 ProcessPoolExecutor,而不是默认 ThreadPoolExecutor
max_workers = os.cpu_count() or 4
with ProcessPoolExecutor(max_workers=max_workers) as pool:
nums = [8_000_000, 9_000_000, 10_000_000, 11_000_000]
futures = [
loop.run_in_executor(pool, cpu_bound, n)
for n in nums
]
results = await asyncio.gather(*futures)
print("results:", results)
stop.set()
await hb_task
if __name__ == "__main__":
start = time.perf_counter()
asyncio.run(main())
print(f"elapsed: {time.perf_counter() - start:.2f}s")
old
协程函数,使用asynic定义的函数:asynic def func,在python3.5引入。
协程对象,执行协程函数()时仅得到协程对象,内部代码不会执行。
import asyncio
async def func():
print('hello world')
# 运行异步函数体的内容,需要将协程对象交给事件循环进行处理。
# loop = asyncio.get_event_loop()
# loop.run_until_complete(func())
asyncio.run(func()) # 与以上两行等价,但asyncio.run在python3.7以上才可用
🚁 await
await + 可等待的对象(包括协程对象、asyncio.Future对象、Task对象)
挂起当前协程(任务),等待IO操作完成之后再继续执行。
import asyncio
async def others():
print("start")
await asyncio.sleep(2)
print("end")
return "返回值"
async def func():
print("first")
res1 = await others()
print("IO请求结束,结果res1为:", res1)
res2 = await others()
print("IO请求结束,结果res2为:", res2)
asyncio.run(func())
🚁 Task对象
在事件循环中,将协程对象封装为Task对象,交给事件循环进行处理。
import asyncio
async def func():
print(1)
await asyncio.sleep(2)
print(2)
return "返回值"
async def main():
print("main开始")
task_list = [
asyncio.create_task(func(), name='n1'),
asyncio.create_task(func(), name='n2') # python3.7引入
]
print("main结束")
done, pending = await asyncio.wait(task_list, timeout=None)
print(done) # Set
for task in done:
print(task.result())
asyncio.run(main())
🚁 asyncio.Future对象
等待异步结果,Task的基类,更底层,一般不会直接用。
🚁 concurrent.futures.Future对象
使用线程池、进程池实现异步操作时用到的对象,主要在异步与同步间转换时使用,如异步编程时,遇到不支持异步的第三方组件。
import time
import asyncio
import concurrent.futures
def func1():
# 某个耗时操作
time.sleep(2)
return "test"
async def main():
loop = asyncio.get_running_loop()
# 内部先调用ThreadPoolExecutor的submit方法去线程池(默认是线程池)中申请一个线程去执行func1函数,返回concurrent.futures.Future对象
# 再调用asyncio.wrap.future,将其包装为asyncio.Future对象
fut = loop.run_in_executor(None, func1) # 其他参数可传入func1的传参
result = await fut
print('default thread pool', result)
# 在线程池中使用
with concurrent.futures.ThreadPoolExecutor() as pool:
result = await loop.run_in_executor(pool, func1)
print('custom thread pool', result)
# 在进程池中使用
with concurrent.futures.ProcessPoolExecutor() as pool:
result = await loop.run_in_executor(pool, func1)
print('custom process pool', result)
asyncio.run(main())