选型不纠结:在高并发场景下如何权衡 asyncio、线程与进程(附可落地的实践清单)
面向对象:既要打好并发基础的初学者,又想在生产环境中稳住吞吐与稳定性的资深开发者
目标:给出一套可操作的选型方法与工程化范式,并提供可直接粘贴使用的代码片段
开篇引入:并发的“路口选择题”
从最早的自动化脚本,到 Web 后端、数据采集、实时处理与 AI 工程,Python 在高并发场景下的存在感与日俱增。与此同时,问题也随之而来:到底用 asyncio 还是线程池?CPU 吃紧时需不需要进程池?
这篇文章不止讲“概念”,更给出可执行的选型流程、实践清单与踩坑解法。核心观点提前亮出:
- I/O 密集、高连接数、库支持良好 ⇒ 优先
asyncio。 - I/O 阻塞库(难以替换)、少量并发 ⇒ 线程池。
- CPU 密集(避开 GIL)⇒ 进程池。
- 异步主流程 + 少量阻塞点 ⇒
asyncio.to_thread/run_in_executor混合。 - 混合流水线:线程/异步负责 I/O,进程负责重计算。
一、基础知识速写:为什么会有 3 条路
- 线程(Thread):同一进程内共享内存,上下文切换轻,适合 I/O;但受 GIL 限制,CPU 密集不增益;要考虑线程安全。
- 进程(Process):独立内存空间,可并行跑满多核;但创建/通信成本高,需可序列化(pickle),适合 CPU 密集。
asyncio(协程):单线程事件循环,切换成本更低,适合大量并发 I/O;要求使用异步库,阻塞调用会卡整个 loop。
一句话:选
asyncio看库生态,选线程看阻塞点与线程安全,选进程看 CPU 与内存边界。
二、三选一的“十步走”决策树(可打印贴墙)
-
你的瓶颈是 I/O 还是 CPU?
- 主要等待网络/磁盘/数据库 ⇒ I/O
- 主要算公式/解码/特征工程 ⇒ CPU
-
I/O 类型是否异步友好(有成熟的 async 库)?
- 有:HTTP(
aiohttp/httpx)、PostgreSQL(asyncpg)、Redis(redis.asyncio)、S3(aiobotocore)等 ⇒asyncio - 无:老旧/私有 SDK、
requests、某些驱动 ⇒ 优先线程池包裹
- 有:HTTP(
-
需要同时维护海量并发连接(10k+)?
- 需要 ⇒
asyncio(配合uvloop、asyncio.Semaphore、asyncio.Queue) - 不需要(例如千级并发)⇒ 线程池也可胜任
- 需要 ⇒
-
CPU 计算是否显著(>1 核死命跑)?
- 是 ⇒ 进程池(
ProcessPoolExecutor),异步/线程只做 I/O - 否 ⇒ 保持
asyncio或线程
- 是 ⇒ 进程池(
-
生态迁移成本
- 能换到 async 驱动:就换(收益长期)
- 不能:线程池 + 超时 + 重试 + 限流
-
混合模式是否更优?
- 异步主干 +
to_thread“吃掉”少量阻塞 - 线程/异步调度数据,进程批量算
- 异步主干 +
-
平台限制
- Windows/macOS 默认
spawn;Linux 常见fork。Pickle 代价、可序列化性要评估
- Windows/macOS 默认
-
可观测性准备好吗?
- 指标:吞吐、TP95/TP99、失败率、超时率、队列长度、FD/线程数/内存
-
故障策略
- 超时、重试、熔断、降级、取消(
cancel_futures)、优雅关停
- 超时、重试、熔断、降级、取消(
-
回归串行基线
- 并发前先测串行指标,确保“并发真的带来收益”
三、asyncio:当 I/O 与连接数成为主旋律
3.1 典型场景
- 高并发 HTTP 客户端/服务端、推送长连接(WebSocket)、爬虫、实时数据转发
- 数据库/缓存异步驱动(
asyncpg、redis.asyncio、motorfor MongoDB)
3.2 最小闭环示例:限速 + 超时 + 重试 + 背压
import asyncio
import random
from contextlib import asynccontextmanager
import aiohttp
@asynccontextmanager
async def get_session():
timeout = aiohttp.ClientTimeout(total=5)
async with aiohttp.ClientSession(timeout=timeout) as s:
yield s
async def fetch(session, url, sem, retries=2):
async with sem: # 背压阀
for _ in range(retries + 1):
try:
async with session.get(url) as r:
return r.status, await r.text()
except Exception:
await asyncio.sleep(0.2 * random.random())
raise RuntimeError(f"failed: {
url}")
async def main(urls):
sem = asyncio.Semaphore(200) # 最大并发
async with get_session() as s:
tasks = [asyncio.create_task(fetch(s, u, sem)) f

最低0.47元/天 解锁文章

被折叠的 条评论
为什么被折叠?



