如何使用Python开发异步服务_Python异步IO核心原理【技巧】

Python异步服务依赖事件循环、协程和非阻塞IO协同工作,核心是避免单线程被IO阻塞;async/await用于定义和等待协程,需配合异步库(如httpx、asyncpg)和正确并发控制(如Semaphore、gather),误用同步代码会拖垮性能。

Python异步服务不是靠多线程或多进程堆出来的,而是靠事件循环 + 协程 + 非阻塞IO协同工作的。核心在于让单个线程不被IO卡住,持续处理其他任务。

理解async/await和协程的本质

async def定义的函数不是普通函数,它返回一个协程对象(coroutine object),只有被事件循环调度执行时才会真正运行。await只能出现在async函数里,且后面必须是可等待对象(如另一个协程、asyncio.Future、asyncio.Task,或实现了__await__的对象)。

常见误区:
– 把同步代码直接加async/await不会变快;
– await time.sleep(1)会报错,得用await asyncio.sleep(1);
– 没有await的协程不会执行,就像定义了函数但没调用。

简单例子:

import asyncio

async def fetch_data(): print("开始请求") await asyncio.sleep(1) # 模拟非阻塞网络延迟 return "数据已就绪"

async def main(): result = await fetch_data() # 这里才真正执行并等待 print(result)

启动事件循环

asyncio.run(main())

用asyncio构建基础异步HTTP服务

推荐用Starlette或FastAPI(底层基于Starlette+Pydantic),它们原生支持async视图,比Flask/Tornado更轻量、更现代。

关键点:
– 路由函数声明为async def;
– 数据库操作要用异步驱动(如asyncpg、aiomysql、tortoise-orm);
– 文件读写避免open(),改用aiofiles;
– 不要混用阻塞调用(如requests.get、json.loads大文件),否则会拖垮整个事件循环。

示例(FastAPI):

from fastapi import FastAPI
import httpx

app = FastAPI()

@app.get("/items/") async def read_items(): async with httpx.AsyncClient() as client: resp = await client.get("https://www./link/c2148796071914983ed6b6e9dbbff735") return resp.json()

手动管理事件循环与并发控制

日常开发中多数用asyncio.run()就够了,但在嵌入式环境、测试或与同步框架(如Django)集成时,可能需要更精细控制。

常用技巧:
– asyncio.create_task()把协程包装成Task,立即调度,适合“发出去就不用等”的场景;
– asyncio.gather()并发运行多个协程,全部完成才返回结果;
– asyncio.wait()更灵活,可设timeout、first_completed等策略;
– 用asyncio.Semaphore限制并发数,防爆资源(比如同时最多5个数据库连接)。

并发限流示例:

sem = asyncio.Semaphore(3)

async def limited_fetch(url): async with sem: # 最多3个同时执行 async with httpx.AsyncClient() as client: return await client.get(url)

避开常见陷阱

异步不是银弹,用错反而更慢甚至出错:

  • 别在async函数里调用time.sleep()、print()太多(虽不阻塞,但高频IO影响性能)
  • 不要用threading.Lock,换asyncio.Lock;不要用queue.Queue,换asyncio.Queue
  • 数据库连接池必须用异步驱动,否则一个await就把整个循环卡死
  • 调试时print位置很重要——协程未await前只是对象,print不会触发执行
  • 日志建议用structlog或logging配置异步handler,避免阻塞

基本上就这些。异步IO本身不复杂,难的是打破同步思维惯性,养成“哪里可能阻塞,就查它有没有async版本”的习惯。