Python 后端进阶:并发编排与性能工程

2026年 阅读约 9 分钟 面试指南 · Python

Python 后端进阶面试指南:asyncio 事件循环与协程编排、多进程/多线程/协程选型、性能工程(cProfile/异步框架/连接池)、工程化(类型标注/mypy/虚拟环境),附代码示例。 Python 后端 asyncio 并发 性能 面试

一句话总结

Python 并发的真相是"分工":CPU 密集用多进程绕开 GIL,I/O 密集用 asyncio 协程以单线程支撑海量并发,线程仅适合少量阻塞 I/O。进阶能力 = 把事件循环玩透 + 用对武器 + 用数据驱动性能优化。

场景首选原因
CPU 密集(计算/压缩)multiprocessing绕过 GIL 真并行
I/O 密集(网络/DB)asyncio协程低开销高并发
少量阻塞调用threading简单,受 GIL 限制

asyncio 事件循环深入

事件循环(Event Loop)是 asyncio 的心脏:它维护一个就绪队列,不断地"取一个协程跑一会、遇 await 就挂起让出、等 I/O 就绪再恢复"。单线程内通过协作式多任务实现并发。

  • async/await 标记协程;await 处让出控制权,不阻塞线程。
  • asyncio.create_task() 把协程调度到循环并发执行;gather 并发收集结果。
  • 陷阱:在协程里调用同步阻塞函数(如 time.sleep、未异步化的 DB 驱动)会卡住整个循环。
import asyncio

async def fetch(name, delay):
    await asyncio.sleep(delay)   # 非阻塞让出
    return f"{name} done"

async def main():
    # 并发执行而非串行
    results = await asyncio.gather(
        fetch("A", 1), fetch("B", 2), fetch("C", 1)
    )
    print(results)

asyncio.run(main())

多进程 / 线程 / 协程选型

GIL(全局解释器锁):同一时刻只有一个线程执行 Python 字节码,所以多线程无法在 CPU 密集任务上真并行。

  • 多进程:每进程独立 GIL 与内存,适合 CPU 密集;代价是进程间通信(Queue/Manager)开销大。
  • 线程:受 GIL 限制,仅适合 I/O 等待期间并发;适合包裹少量阻塞库。
  • 协程:用户态调度,单线程数万并发,内存占用极低;是后端高并发 I/O 的首选。

实践中常组合:多进程 + 每进程一个事件循环(如 gunicorn + uvicorn worker),横向用进程、纵向用协程,最大化多核与并发。

性能工程

性能优化必须先度量后优化。用 cProfile 定位热点,避免凭直觉:

import cProfile
cProfile.run("heavy_compute()", sort="cumulative")
  • 异步框架:FastAPI/Starlette 基于 asyncio,单实例即可扛高并发请求;配合 async def 路由与异步 DB 驱动(asyncpg/aiomysql)。
  • 数据库:用连接池(SQLAlchemy asyncio engine)避免频繁建连;批量写代替循环单条。
  • 序列化:orjson 替代 json 提速明显;热点路径避免不必要对象分配。
  • C 扩展:超热点可用 Cython 或调用 numpy 向量化,进一步榨干 CPU。

工程化

大型 Python 后端必须重视工程化,否则类型与依赖失控:

  • 类型标注 + mypy:全量标注函数签名,CI 里跑 mypy --strict 提前抓类型错误,是降低线上事故最有效的手段之一。
  • 虚拟环境与锁定:venv + pip-tools/poetry 锁定依赖,保证环境可复现。
  • 异步 ORM:用 SQLAlchemy 2.0 async 或 Tortoise-ORM,路由内全程 async。
  • 结构化日志:用 structlog/loguru 输出 JSON 日志,便于采集与链路关联。

面试模拟

Q1:asyncio 里调用 time.sleep 会怎样?
答:time.sleep 是同步阻塞,会卡住整个事件循环,所有协程都停摆;应改用 await asyncio.sleep。

Q2:为什么 CPU 密集任务用多线程没用?
答:GIL 让同一时刻仅一个线程执行字节码,多线程在 CPU 密集下是交替而非并行,甚至因切换更慢;应用多进程。

Q3:FastAPI 为什么比传统 Flask 并发高?
答:FastAPI 基于 asyncio,每个请求在 I/O 等待时让出循环处理其他请求;Flask 默认同步阻塞,并发靠线程/进程数,开销大。

Q4:协程比线程轻量在哪?
答:协程是用户态调度,单协程栈仅 KB 级,可轻松开数万;线程由内核调度,默认栈 MB 级,上下文切换成本高。