asyncio比threading更适合I/O密集型任务,因其基于单线程事件循环调度协程,避免线程切换开销与GIL争抢;而threading存在创建/同步成本及系统调度压力。

asyncio 为什么比 threading 更适合 I/O 密集型任务
因为 asyncio 在单线程内用事件循环调度协程,避免了线程切换开销和 GIL 争抢;而 threading 在 I/O 等待时虽能释放 GIL,但创建、调度、同步(如 Lock)本身有固定成本,且线程数多时 OS 调度压力明显上升。
典型场景:同时发起 1000 个 HTTP 请求 —— asyncio 常用 aiohttp,内存占用低、启动快;threading 配 requests 容易触发 OSError: [Errno 24] Too many open files,还得配线程池控数量。
- 协程是用户态轻量调度,一个线程可跑数万协程;线程是内核态实体,Linux 默认每进程约 1024 线程上限
-
await是显式让出控制权的点,I/O 暂停时不阻塞整个事件循环;threading中time.sleep()或未优化的requests.get()会真阻塞线程 - 协程间共享变量无需加锁(单线程),线程间共享需
threading.Lock或queue.Queue,一漏就出竞态
什么时候该坚持用 threading 而不是 asyncio
当任务里混着不可协程化的阻塞调用,比如某些 C 扩展库(cv2.VideoCapture().read())、旧版数据库驱动(psycopg2 同步版)、或必须调用的第三方 CLI 工具(subprocess.run() 同步阻塞模式)—— 这些没法 await,硬塞进 asyncio 只能靠 loop.run_in_executor() 丢给线程池,反而增加胶水代码和上下文切换。
- 已有稳定多线程代码,无性能瓶颈,没必要重构成 async
- 需要 CPU 密集型并行(如图像处理),
threading受 GIL 限制,此时应选multiprocessing,而非 asyncio - 依赖库不支持异步(如部分金融/硬件 SDK),强行套
asyncio.to_thread()可能掩盖超时、中断等异常行为
常见错误:把同步函数直接 await
写 await requests.get("https://...") 会报 TypeError: object Response can't be used in 'await' expression —— 因为 requests 是纯同步库,返回的是 Response 对象,不是 Awaitable。
立即学习“Python免费学习笔记(深入)”;
SkillSub Pro - Python 题解与代码注释双功能技能功能概述SkillSub Pro - Python 题解与代码注释双功能技能是一项面向实际任务的技能,主要用于SkillSub Pro 是一个 Python 题解生成与代码注释的 双功能合体技能 ,专为学生、算法学习者和开发者设计;✅ 一个技能,两种用途 :;核心要点📝 题解模式 :输入题目/题号,自动生成完整 Python 题解(含详细注释、解题思路、复杂度分析);💬 注释模式 :输入 Python 代码,自动添加详细中。它将相关步骤、
正确做法是换库或包装:
- 用
aiohttp替代requests:await session.get(url) - 用
httpx.AsyncClient,API 接近requests,支持await client.get() - 非换库不可时,用
await loop.run_in_executor(None, requests.get, url),但注意这会退化成线程模型,失去 async 轻量优势
asyncio.run() 和 asyncio.create_task() 的误用边界
asyncio.run() 是入口函数,每次调用都新建事件循环、运行完就关闭;在已存在的 event loop 中(如 Jupyter、FastAPI 生命周期内)再调用会抛 RuntimeError: asyncio.run() cannot be called from a running event loop。
create_task() 是把协程提交到当前 loop 调度,但若没 await 或没加到 task 集合里,可能被 GC 掉,导致“协程写了却不执行”。
- 脚本顶层用
asyncio.run(main())没问题;Web 框架里别这么干 -
asyncio.create_task(coro)后,记得保存引用(如放进列表),或用asyncio.gather(*tasks)等待完成 - 临时起一个协程又不想等它?用
asyncio.create_task(coro, name="xxx")并确保它不会因异常静默退出(加 try/except 或设asyncio.current_task().set_name()方便调试)
协程的生命周期比线程更“隐形”,没显式 await、没被 gather、没被 task 引用,就真的消失了——这点比线程难 debug。

















