python进阶-异步编程(大模型转型必会)

1. 异步编程简介

在IO密集型任务(如网络请求、数据库操作)中,传统的同步代码会阻塞等待,导致CPU空闲。asyncio提供基于事件循环的单线程并发方案:遇到IO等待时自动切换任务,从而大幅提升吞吐量。尤其适合AI服务中需要同时调用多个外部API的场景。


2. 核心概念与基本语法

2.1 定义协程

使用async def定义协程函数,调用它返回一个协程对象,不会立即执行。

import asyncio

async def say_hello():
    """一个简单的协程,打印Hello,等待1秒,再打印World"""
    print("Hello")
    await asyncio.sleep(1)   # 模拟IO等待
    print("World")

2.2 运行协程

Python 3.7+使用asyncio.run()启动事件循环并执行一个协程。

# 保存为 demo1.py 并运行
import asyncio

async def say_hello():
    print("Hello")
    await asyncio.sleep(1)
    print("World")

if __name__ == "__main__":
    asyncio.run(say_hello())

2.3 await关键字

await挂起当前协程,等待另一个可等待对象(协程、Task、Future)完成。

import asyncio

async def say_hello():
    print("Hello")
    await asyncio.sleep(1)
    print("World")

async def main():
    print("开始")
    await say_hello()      # 等待say_hello执行完毕
    print("结束")

asyncio.run(main())

2.4 创建任务(Task)

asyncio.create_task()将协程包装为任务,使其并发执行。

import asyncio

async def say_after(delay, msg):
    await asyncio.sleep(delay)
    print(msg)

async def main():
    # 创建两个任务,它们将并发执行
    task1 = asyncio.create_task(say_after(1, "Hello"))
    task2 = asyncio.create_task(say_after(2, "World"))
    
    print("任务已创建,等待完成...")
    await task1   # 等待task1完成
    await task2   # 等待task2完成
    print("所有任务完成")

asyncio.run(main())

3. 并发执行多个协程

3.1 asyncio.gather() —— 收集结果

并发运行多个可等待对象,返回所有结果的列表(顺序与输入一致)。

import asyncio

async def fetch_data(delay, name):
    await asyncio.sleep(delay)
    return f"{name} 完成"

async def main():
    results = await asyncio.gather(
        fetch_data(1, "任务A"),
        fetch_data(2, "任务B"),
        fetch_data(1.5, "任务C")
    )
    print(results)   # ['任务A 完成', '任务B 完成', '任务C 完成']

asyncio.run(main())

3.2 asyncio.wait() —— 细粒度控制

可以设置超时、等待条件(FIRST_COMPLETED等),返回完成和未完成的任务集合。

import asyncio

async def fetch_data(delay, name):
    await asyncio.sleep(delay)
    return f"{name} 完成"

async def main():
    tasks = {asyncio.create_task(fetch_data(i, f"任务{i}")) for i in [3, 1, 2]}
    # 等待第一个任务完成,超时2秒
    done, pending = await asyncio.wait(tasks, timeout=2, return_when=asyncio.FIRST_COMPLETED)
    
    print(f"已完成的任务数: {len(done)}")
    for task in done:
        print(task.result())
    
    # 取消未完成的任务
    for task in pending:
        task.cancel()
        print("已取消一个任务")

asyncio.run(main())

3.3 asyncio.as_completed() —— 按完成顺序处理

返回一个迭代器,按任务完成的先后顺序产出结果。

import asyncio

async def fetch_data(delay, name):
    await asyncio.sleep(delay)
    return f"{name} 完成"

async def main():
    tasks = [fetch_data(i, f"任务{i}") for i in [3, 1, 2]]
    for coro in asyncio.as_completed(tasks):
        result = await coro
        print(result)   # 按实际完成顺序打印:任务1完成、任务2完成、任务3完成

asyncio.run(main())

4. 可等待对象(略)

可等待对象包括:协程、Task、Future。通常我们只需处理协程和Task。


5. 异步上下文管理器(async with

用于管理需要异步初始化和清理的资源。自定义异步上下文管理器需实现__aenter____aexit__

import asyncio

class AsyncResource:
    async def __aenter__(self):
        print("正在获取资源...")
        await asyncio.sleep(1)
        print("资源已获取")
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        print("正在释放资源...")
        await asyncio.sleep(1)
        print("资源已释放")

async def main():
    async with AsyncResource() as res:
        print("使用资源中...")

asyncio.run(main())

实际应用:aiohttp的ClientSession、异步数据库连接等。


6. 异步迭代器(async for

用于异步产生数据。可以使用异步生成器(yieldasync def中)更简洁。

import asyncio

# 异步生成器
async def async_range(n):
    for i in range(n):
        await asyncio.sleep(0.1)   # 模拟异步等待
        yield i

async def main():
    async for num in async_range(5):
        print(num)   # 0 1 2 3 4

asyncio.run(main())

7. 同步与异步的桥梁(run_in_executor

在协程中执行阻塞的同步代码(如CPU密集型计算或传统同步库)时,应将其放入线程池,避免阻塞事件循环。

import asyncio
import time

def blocking_io():
    """模拟阻塞IO操作(如文件读写、同步请求)"""
    time.sleep(2)
    return "阻塞操作结果"

async def main():
    loop = asyncio.get_running_loop()
    # 在线程池中运行阻塞函数
    result = await loop.run_in_executor(None, blocking_io)
    print(result)

asyncio.run(main())

8. 异步队列(asyncio.Queue

用于在多个协程之间安全传递数据,典型的生产者-消费者模型。

import asyncio
import random

async def producer(queue, n):
    for i in range(n):
        item = f"物品{i}"
        await queue.put(item)
        print(f"生产了 {item}")
        await asyncio.sleep(random.random())
    await queue.put(None)   # 发送结束信号

async def consumer(queue):
    while True:
        item = await queue.get()
        if item is None:
            break
        print(f"消费了 {item}")
        queue.task_done()

async def main():
    queue = asyncio.Queue(maxsize=5)
    prod_task = asyncio.create_task(producer(queue, 10))
    cons_task = asyncio.create_task(consumer(queue))
    await asyncio.gather(prod_task, cons_task)

asyncio.run(main())

9. 超时与任务取消

9.1 超时(asyncio.wait_for

设置协程的最大执行时间,超时抛出TimeoutError

import asyncio

async def long_operation():
    await asyncio.sleep(10)
    return "完成"

async def main():
    try:
        result = await asyncio.wait_for(long_operation(), timeout=2.0)
    except asyncio.TimeoutError:
        print("操作超时")

asyncio.run(main())

9.2 取消任务(cancel()

调用task.cancel()请求取消任务,协程内可捕获CancelledError进行清理。

import asyncio

async def cancellable_task():
    try:
        await asyncio.sleep(10)
    except asyncio.CancelledError:
        print("任务被取消,进行清理...")
        raise   # 必须重新抛出以标记任务取消

async def main():
    task = asyncio.create_task(cancellable_task())
    await asyncio.sleep(1)
    task.cancel()
    try:
        await task
    except asyncio.CancelledError:
        print("任务已取消")

asyncio.run(main())

9.3 屏蔽取消(asyncio.shield

保护某个协程不被取消,即使外层任务被取消。

import asyncio

async def critical_operation():
    await asyncio.sleep(3)
    print("关键操作完成")

async def main():
    task = asyncio.create_task(critical_operation())
    await asyncio.sleep(1)
    # 即使主任务被取消,shield内部仍会继续执行
    await asyncio.shield(task)
    # 如果需要取消,可以单独取消
    task.cancel()

asyncio.run(main())

10. 限制并发数量(信号量)

使用asyncio.Semaphore控制同时执行的任务数量,例如限制对API的并发请求。

import asyncio
import aiohttp   # 需要安装:pip install aiohttp

async def fetch_url(sem, session, url):
    async with sem:
        print(f"正在请求: {url}")
        async with session.get(url) as response:
            return await response.text()

async def main():
    urls = ["http://example.com"] * 10   # 模拟10个相同请求
    sem = asyncio.Semaphore(3)   # 最多同时3个请求
    
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url(sem, session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        print(f"获取到 {len(results)} 个响应")

# 注意:example.com可能无法真正连接,仅演示结构
# 实际使用时请替换为真实URL
asyncio.run(main())

11. 在AI服务中的应用场景

11.1 并发调用多个AI模型API

假设需要同时对文本进行三种不同的AI处理。

import asyncio
import aiohttp

async def call_ai_api(session, text, model_name):
    """模拟调用AI API"""
    url = f"https://api.example.com/{model_name}/predict"
    payload = {"text": text}
    async with session.post(url, json=payload) as resp:
        return await resp.json()

async def handle_request(text):
    async with aiohttp.ClientSession() as session:
        tasks = [
            call_ai_api(session, text, "sentiment"),
            call_ai_api(session, text, "ner"),
            call_ai_api(session, text, "summary")
        ]
        results = await asyncio.gather(*tasks)
        return {"text": text, "sentiment": results[0], "ner": results[1], "summary": results[2]}

async def main():
    result = await handle_request("I love Python async programming!")
    print(result)

# 实际运行时需要真实API端点,此处仅展示结构
asyncio.run(main())

11.2 异步数据库访问(使用asyncpg)

需要安装asyncpgpip install asyncpg

import asyncio
import asyncpg

async def fetch_users():
    conn = await asyncpg.connect(
        user='your_user', password='your_pass',
        database='your_db', host='localhost'
    )
    rows = await conn.fetch('SELECT id, name FROM users LIMIT 10')
    await conn.close()
    return rows

async def main():
    users = await fetch_users()
    for user in users:
        print(dict(user))

# 注意:需替换为真实数据库连接信息
# asyncio.run(main())

11.3 异步微服务网关

同时向后端多个服务发起请求,合并响应。

import asyncio
import aiohttp

async def fetch_service(session, service_url, payload):
    async with session.post(service_url, json=payload) as resp:
        return await resp.json()

async def gateway(request_data):
    services = [
        "http://ai-service1/predict",
        "http://ai-service2/predict",
        "http://ai-service3/predict"
    ]
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_service(session, url, request_data) for url in services]
        results = await asyncio.gather(*tasks)
        return results

async def main():
    data = {"input": "test data"}
    combined = await gateway(data)
    print(combined)

asyncio.run(main())

11.4 流式数据处理(异步迭代器 + 队列)

从消息队列消费消息,并发处理。

import asyncio
import random

async def message_generator(queue):
    """模拟消息生产者"""
    for i in range(20):
        await asyncio.sleep(random.random() * 0.5)
        msg = f"消息{i}"
        await queue.put(msg)
        print(f"放入: {msg}")
    await queue.put(None)   # 结束信号

async def process_message(msg):
    """模拟AI处理"""
    await asyncio.sleep(random.random())
    return f"处理结果: {msg}"

async def worker(worker_id, queue, results):
    while True:
        msg = await queue.get()
        if msg is None:
            await queue.put(None)   # 传递结束信号给其他worker
            break
        result = await process_message(msg)
        print(f"Worker {worker_id} 处理了 {msg} -> {result}")
        results.append(result)

async def main():
    queue = asyncio.Queue()
    results = []
    
    # 启动生产者和消费者
    producer = asyncio.create_task(message_generator(queue))
    workers = [asyncio.create_task(worker(i, queue, results)) for i in range(3)]
    
    await producer
    await asyncio.gather(*workers)
    print(f"总共处理了 {len(results)} 条消息")

asyncio.run(main())

12. 常见陷阱与调试技巧

12.1 忘记await

import asyncio

async def bad_example():
    # 错误:直接调用协程但没有await,协程不会执行
    asyncio.sleep(1)   # 缺少await,返回一个协程对象,未执行
    print("这行会立即打印")

async def good_example():
    await asyncio.sleep(1)
    print("1秒后打印")

asyncio.run(good_example())

12.2 在协程中使用阻塞IO

import asyncio
import time

async def blocking_mistake():
    time.sleep(2)   # 阻塞整个事件循环!
    print("完成")

async def main():
    # 错误示例:会阻塞2秒,期间无法处理其他任务
    await blocking_mistake()

# 正确做法:使用run_in_executor

12.3 异常处理

import asyncio

async def might_fail(should_fail):
    if should_fail:
        raise ValueError("出错了")
    await asyncio.sleep(1)
    return "成功"

async def main():
    tasks = [might_fail(False), might_fail(True)]
    # 使用return_exceptions=True让gather返回异常对象而非抛出
    results = await asyncio.gather(*tasks, return_exceptions=True)
    for r in results:
        if isinstance(r, Exception):
            print(f"捕获到异常: {r}")
        else:
            print(f"结果: {r}")

asyncio.run(main())

12.4 调试模式

设置环境变量PYTHONASYNCIODEBUG=1或在代码中启用:

asyncio.run(main(), debug=True)

12.5 任务清理

import asyncio

async def worker():
    try:
        while True:
            await asyncio.sleep(1)
    except asyncio.CancelledError:
        print("worker被取消,清理中...")
        raise

async def main():
    task = asyncio.create_task(worker())
    await asyncio.sleep(3)
    task.cancel()
    try:
        await task
    except asyncio.CancelledError:
        print("worker已清理完毕")

asyncio.run(main())

13. 进阶:与Web框架结合(FastAPI示例)

需要安装fastapiuvicornpip install fastapi uvicorn

# server.py
from fastapi import FastAPI
import asyncio
import httpx   # 异步HTTP客户端,需安装:pip install httpx

app = FastAPI()

async def call_ai(prompt: str):
    async with httpx.AsyncClient() as client:
        # 这里替换为真实的AI服务地址
        resp = await client.post("https://api.openai.com/v1/completions", 
                                 json={"prompt": prompt, "max_tokens": 50},
                                 headers={"Authorization": "Bearer YOUR_KEY"})
        return resp.json()

@app.post("/process")
async def process(prompt: str):
    # 并发调用多个模型(这里模拟调用同一个模型两次)
    results = await asyncio.gather(
        call_ai(prompt),
        call_ai(prompt + " (enhanced)")
    )
    return {"prompt": prompt, "results": results}

# 运行:uvicorn server:app --reload

启动服务后,用curl测试:curl -X POST "http://127.0.0.1:8000/process?prompt=Hello"


14. 总结

  • asyncio让Python能够高效处理IO密集型并发任务。
  • 核心模式:定义协程(async def)、创建任务(create_task)、并发执行(gather/wait)。
  • 掌握异步上下文管理器、异步迭代器、队列、信号量等工具,构建复杂系统。
  • 注意将阻塞操作放入线程池,合理处理超时和取消。
  • 在AI服务中,结合aiohttp、异步数据库驱动和异步Web框架,可实现高吞吐量微服务。
posted @ 2026-03-17 15:59  Xiaohu_BigData  阅读(9)  评论(0)    收藏  举报