You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Asyncio视角下requests与aiohttp/httpx兼容差异底层原理

核心结论

requests无法适配asyncio的本质原因是它从底层传输到上层逻辑全链路是阻塞式同步设计,没有任何协程调度的让出点,和它用不用select没有关系;httpx、aiohttp能适配是因为从设计之初就(或通过抽象层)实现了全链路非阻塞、协程友好的逻辑。


先纠正一个认知偏差

你理解的asyncio非阻塞socket工作逻辑基本正确,但漏了最核心的前提:asyncio的事件循环是协作式调度,只有当协程主动await把控制权交还给事件循环时,调度器才能去跑其他任务。只要调用链上有任何一个点没有主动让出控制权,哪怕只是几毫秒的阻塞C级系统调用,整个事件循环所在的线程都会被卡死,所有其他协程都会暂停。

另外不是用了select就能和asyncio兼容:urllib3(requests的默认传输层)内部也会用select做超时判断,但它是自己阻塞在select调用上等IO就绪,根本不会把监听的socket fd交给asyncio的事件循环统一管理,相当于在协程里偷偷跑了一套独立的阻塞IO轮询,本质还是堵死线程。


requests和asyncio不兼容的具体原因

  • 底层socket默认是阻塞模式,读写逻辑完全不适配协程
    requests依赖urllib3创建的socket从一开始就是阻塞模式,所有send()/recv()调用默认会阻塞当前线程直到操作完成,根本不会在IO无法立即完成时挂起当前任务、把socket注册到asyncio的事件循环上、把控制权交还给调度器。就算你手动强行把socket改成非阻塞模式也没用,因为urllib3的读写逻辑根本没有处理非阻塞返回的EAGAIN错误,会直接抛异常崩溃。
  • 全链路都是同步阻塞逻辑,没有协程让出点
    你猜测的“存在大量阻塞中间函数”是对的,但这不是零散的几个函数,是从最底层到上层的全链路:DNS解析用的是阻塞的C级系统调用socket.getaddrinfo、TCP建连/TLS握手是阻塞等待、连接池是基于线程锁实现的同步队列、重定向/认证/cookie处理/解码逻辑全是同步函数,整个调用链走下来没有一个await点,全程占着线程不让出控制权。
  • 核心设计没有预留异步抽象层
    requests第一版发布时Python还没有asyncio生态,整个架构就是为同步线程模型设计的,根本没有把HTTP逻辑和底层传输拆解开,你没法只换个传输层就把它变成异步的。

为什么httpx、aiohttp可以适配asyncio

  • aiohttp从设计之初就是完全基于asyncio生态实现的:底层直接用asyncio封装的非阻塞socket,所有IO操作都会在无法立即完成时await挂起、把控制权交还给事件循环,从DNS解析、连接池管理、协议解析到上层逻辑全是协程友好的实现,没有隐式阻塞点。
  • httpx做了清晰的分层抽象:它把上层HTTP业务逻辑(重定向、认证、cookie处理、编解码)和底层传输层完全拆开,同步模式下用阻塞式传输实现,异步模式下直接切换到基于asyncio的非阻塞传输实现,上层逻辑只需要写一套就能同时兼容同步/异步场景,不需要重复开发。

把requests改造成asyncio兼容版本的难度

这根本不是“替换几个阻塞函数”的小改动,工作量基本等于重写一个新的HTTP客户端:

  1. 首先要彻底抛弃依赖的urllib3传输层,重新基于asyncio实现一整套非阻塞的socket读写、TLS握手、超时控制、连接池管理逻辑
  2. 要把全链路所有阻塞调用全部替换成协程可等待的实现,包括DNS解析、代理连接、数据编解码等所有环节,不能留任何一个会阻塞线程的C级系统调用
  3. 要把所有基于线程锁、线程同步原语实现的组件(连接池、限流、cookie存储)全部替换成asyncio对应的异步原语
  4. 要把整个插件、钩子、中间件系统改造成支持异步函数

等你把以上工作全部做完,得到的产物其实就是现在的httpx异步客户端,除了API风格和requests类似,底层代码和原来的requests已经没有任何关系了。

社区之前出现的grequests、requests-futures这类库本质是把requests的阻塞调用扔到独立线程池里跑,根本不是真的适配asyncio,不仅有线程切换的额外开销,也没法和asyncio生态原生打通,遇到高并发场景还是会遇到线程池耗尽、阻塞事件循环的问题。

另外不要指望用猴子补丁把requests改成asyncio兼容的:类似gevent的猴子补丁是基于greenlet的调度模型实现的,和asyncio的事件循环调度逻辑完全不兼容,强行打补丁只会导致调度错乱、协程卡死。


内容的提问来源于stack exchange,提问作者S.B

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 20:09:16