FastAPI中如果async def和def 路由的区别

在python的整体生态中,虽然已经有很多库支持了异步调用,如可以使用httpx或者aiohttp代替requests库发起http请求,使用asyncio.sleep 代替time.sleep, 但是依然还有很多优秀的第三方库是不支持异步调用也没有可代替的库,那么如何在FastAPI中调用这种没有实现异步的库但是又不阻塞整个系统呢?

查看官方文档,https://fastapi.tiangolo.com/zh/async/ ,最后有个简要的结论

  1. 如果你的函数中有使用await,则函数使用async def 定义。
  2. 如果不知道是否要使用async,则不要用,使用def 就好。

原理:

**在python 中,使用async def 定义的函数是运行在协程中,而多个协程是在一个主线程中的。fastapi中的async def协程路由处理的请求会全部放在main thread,而def 处理的请求每个都有自己的独立线程,fastapi内部会自动为这两种接口进行对应的处理逻辑 **

我们来详细看一下各种情况。本文将使用await asyncio.sleep(5) 来模拟实现了异步操作的库。time.sleep(5) 来模拟只有同步操作的库。

只有同步库

import time  from fastapi import FastAPI, Request  
import asyncio  
import threading  
import uvicorn  app = FastAPI()  @app.middleware("http")  
async def cal_time(req: Request, call_next):  start = time.time()  response = await call_next(req)  process_time = time.time() - start  print(f"接口{req.url.path} use {process_time} sec.")  return response  @app.get("/test1")  
def test1():  print(threading.current_thread(), "test1")  return "test1"  @app.get("/test2")  
def test2():  print(threading.current_thread(), "test2")  time.sleep(5)  return "test2"  if __name__ == '__main__':  uvicorn.run(app, host="127.0.0.1", port=8000)

上面有的fastapi 定义了两个接口,test1接口未执行任何IO操作,只返回一个字符串,用来验证其它接口是否会阻塞test1接口,test2 接口,这里使用time.sleep(5) 来模拟一个阻塞操作。并且同时打印了test1和test2中的线程信息。并且添加了一个中间件cal_time,打印每个请求的耗时。

我们启动应用并看一下此时存在阻塞的接口test2是否会阻塞test1接口?

我们在浏览器中先访问test2接口,之后再请求test1接口,我们来看一下打印

<WorkerThread(AnyIO worker thread, started 11720)> test2
<WorkerThread(AnyIO worker thread, started 22508)> test1
接口/test1 use 0.0019936561584472656 sec.
INFO:     127.0.0.1:56758 - "GET /test1 HTTP/1.1" 200 OK
接口/test2 use 5.0042359828948975 sec.
INFO:     127.0.0.1:56757 - "GET /test2 HTTP/1.1" 200 OK
  1. 由于先请求的test2接口,所以先打印出了test2接口的线程信息,在11720 线程上执行。
  2. 之后请求了test1接口,这时打印了test1接口所运行的线程,线程号为22508。
  3. 此时 test1 接口并没有因为test2接口的time.sleep(5) 而阻塞,而是直接返回了test1,显示耗时0.001秒。
  4. 在停了5秒钟以后,接口test2完成了响应,显示耗时5秒。

有异步库

在只有同步调用库的情况下,上面的代码运行很好,接口之间并没有相互阻塞,接下来我们看一下存在异步库的情况。

import time  from fastapi import FastAPI, Request  
import asyncio  
import threading  
import uvicorn  app = FastAPI()  @app.middleware("http")  
async def cal_time(req: Request, call_next):  start = time.time()  response = await call_next(req)  process_time = time.time() - start  print(f"接口{req.url.path} use {process_time} sec.")  return response  @app.get("/test1")  
def test1():  print(threading.current_thread(), "test1")  return "test1"  @app.get("/test2")  
async def test2():  print(threading.current_thread(), "test2")  await asyncio.sleep(5)  return "test2"  if __name__ == '__main__':  uvicorn.run(app, host="127.0.0.1", port=8000)

这里修改test2方法,使用 await asyncio.sleep(5) 模拟异步阻塞操作,由于这里使用了await, 所以需要将test2改为async def,这时还是先访问test2接口,再访问test1接口,此时test2中的异步IO操作并依然没有阻塞test1接口,我们再来看一下请求的打印输出。


  1. 首先访问test2接口,打印了test2接口的线程信息。
  2. 接下来访问test1接口,打印了test1接口的线程信息。
  3. 由于test2接口并不会阻塞test1接口,所以这里test1接口完成请求,耗时0.003秒。
  4. 过了5秒钟以后,接口2阻塞结束,打印耗时5秒。

两次接口test2都不会阻塞test1,这是为什么呢?以前在使用Tornado或者Sanic开发时,如果某个接口里调用了同步库,如requests.get,那么如果处理不当是会阻塞整个系统进程的。

我们来仔细看一下程序的打印输出,在第一次使用time.sleep(5) 模拟IO操作时,打印的线程信息为

<WorkerThread(AnyIO worker thread, started 11720)> test2
<WorkerThread(AnyIO worker thread, started 22508)> test1

我们看到两次请求是在不同的线程中运行的,所以即使某个接口请求中存在阻塞操作,也不会影响到其它的线程。

再看第二次使用await asyncio.sleep(5) 模拟IO操作时的线程信息。

<_MainThread(MainThread, started 4501536256)> test2
<WorkerThread(AnyIO worker thread, started 123145549803520)> test1

依然是在两个不同的线程中运行的,所以也不会相互阻塞。

但是我们在横向比较两次请求中的test2的线程信息

<WorkerThread(AnyIO worker thread, started 11720)> test2
<_MainThread(MainThread, started 4501536256)> test2

第一次在使用def 定义函数时,是在WorkerThread中运行的,第二次使用async def 显示是在MainThread 中运行的,这也就说明了一个问题,在python 中,使用async def 定义的函数是运行在协程中,而多个协程是在一个主线程中的。

容易出的问题

通过上面的实践,我们再来看最开始时,FastAPI官网给出的结论:

  1. 如果你的函数中有使用await,则函数使用async def 定义。
  2. 如果不知道是否要使用async,则不要用,使用def 就好。

这个结论看起来比较简单明了,但是我们也看出一些问题,如果存在await,则必须要定义在async def 函数中,这个好说,如果没有定义在async def 函数中,则语法都过不去。对于没有存在异步操作的库,我们应不应该定义在async中呢?我们来实践一下。修改test2函数。

import time  from fastapi import FastAPI, Request  
import asyncio  
import threading  
import uvicorn  
from uvicorn.config import LOGGING_CONFIG  app = FastAPI()  @app.middleware("http")  
async def cal_time(req: Request, call_next):  start = time.time()  response = await call_next(req)  process_time = time.time() - start  print(f"接口{req.url.path} use {process_time} sec.")  return response  @app.get("/test1")  
def test1():  print(threading.current_thread(), "test1")  return "test1"  @app.get("/test2")  
async def test2():  print(threading.current_thread(), "test2")  time.sleep(5)  return "test2"  if __name__ == '__main__':  LOGGING_CONFIG["formatters"]["access"][  "fmt"] = '%(asctime)s %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s'  uvicorn.run(app, host="127.0.0.1", port=8000)

这次我们打印一下请求的具体时间,由于默认的uvicorn 日志没有打印具体的时间,所以这里通过LOGGING_CONFIG重新定义一下日志的格式。在test2函数这时我们使用time.sleep(5) 替换掉await asyncio.sleep(5), 这时依然使用async def 定义test2函数,再访问一下test2和test1接口。

此时我们看到的现象为test1接口被test2接口给阻塞住了,但是由上面的实验得出来的结论,使用def 定义的函数应该是单启一个线程执行,可是为什么还被test2给阻塞了呢?

我们详细看一下输出:

<_MainThread(MainThread, started 3768)> test2
接口/test2 use 5.008129358291626 sec.
2023-11-29 11:33:12,724 INFO:     127.0.0.1:64011 - "GET /test2 HTTP/1.1" 200 OK<WorkerThread(AnyIO worker thread, started 20056)> test1
接口/test1 use 0.006971836090087891 sec.
2023-11-29 11:33:12,730 INFO:     127.0.0.1:64012 - "GET /test1 HTTP/1.1" 200 OK

输出分为两部分,一个是访问test2的输出,一个是访问test1的输出。

  1. test2的输出,首先打印出运行test2函数的线程,由于test2是async def 定义的, 所以这个协程是跑在_MainThread中。
  2. 此时test2函数运行到了time.sleep(5) 处,阻塞了整个系统,使得整个系统无法再接收新来的请求。
  3. 这时新发来了一个test1接口的请求,这时由于test2中的time.sleep 阻塞了整个系统,所以这时test1请求也被阻塞了,并不是test1函数被阻塞了,而是现在都无法开始执行test1接口函数。
  4. 过了5秒钟,执行完time.sleep(5) 代码,打印输出 2023-11-29 11:33:12,724 INFO: 127.0.0.1:64011 - "GET /test2 HTTP/1.1" 200 OK, 系统接着开始处理test1接口请求。
  5. 由于 test1使用def 定义,所以这里跑在了一个新的子线程中,WorkerThread,且test1没有任何IO阻塞操作,所以此时很快的完成了响应。打印耗时0.006。

这也就解释了为什么先访问阻塞的test2接口,再访问非阻塞的test1接口,即使test1接口使用def 定义也会被卡住的情况。

通过上面的实验,我们可以得出,如果存在同步阻塞的IO操作,不要 放到async def 函数中。

test1函数没有阻塞的IO操作,使用async def 或者 def 定义都可以,看官方的文档应该是推荐放到def 中,但是由于def 定义的函数在运行的时候需要单启一个线程(从线程池中获取),会有一些额外的开销,如果访问量小的话应该没有多大影响。

结论

在FastAPI中,如果使用async def 定义的函数,里面的IO操作均要实现异步操作await,如果要使用同步的IO操作,需要使用def 定义函数。简单来讲用下图表示

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1751.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

架构师备考-非关系型数据库

基础理论 CAP 理论 C&#xff08;Consistency&#xff09;一致性。一致性是指更新操作成功并返回客户端完成后&#xff0c;所有的节点在同一时间的数据完全一致&#xff0c;与ACID 的 C 完全不同。A &#xff08;Availability&#xff09;可用性。可用性是指服务一直可用&…

奥云学院应邀参加“第二届中国县域经济投资高峰论坛”

论坛聚焦战略&#xff0c;县域经济迎来新机遇 10月28日&#xff0c;由中国投资协会主办的第二届中国县域经济投资高峰论坛在北京盛大召开。本次论坛以“产业资本助力县域经济高质量发展”为主题&#xff0c;汇聚政府、企业、金融机构和学术专家等多方资源&#xff0c;集中探讨…

飞牛NAS docker compose环境下自建远程桌面服务:rustdesk

&#x1f6e9;️前言 由于国内向日葵、todesk等应用的日渐模糊&#xff0c;恰巧我们已经实现了ipv6的内网穿透&#xff0c;而且在国内ipv6的延迟极低&#xff0c;加上本次介绍的开源远程桌面项目Rustdesk&#xff0c;简直是绝配。 这个项目比较简单&#xff0c;话不多说&…

算法:查找

算法 1. 顺序查找和折半查找1.1 顺序查找1.2 折半查找1.3 索引顺序查找 2. 树表查找2.1 查找2.2 插入 3. 哈希表及哈希查找3.1 哈希造表3.2 处理冲突开放定址法链地址法 3.3 哈希查找 查找是非数值数据处理中一种基本运算&#xff0c;查找运算的效率与查找表所采用的数据结构和…

Istio基本概念及部署

一、Istio架构及组件 Istio服务网格在逻辑上分为数据平面和控制平面。 控制平面&#xff1a;使用全新的部署模式&#xff1a;Istiod&#xff0c;这个组件负责处理Sidecar注入&#xff0c;证书颁发&#xff0c;配置管理等功能&#xff0c;替代原有组件&#xff0c;降低复杂度&…

OpenCV视觉分析之目标跟踪(8)目标跟踪函数CamShift()使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 找到物体的中心、大小和方向。 CamShift&#xff08;Continuously Adaptive Mean Shift&#xff09;是 OpenCV 中的一种目标跟踪算法&#xff0…

gradlew命令打包报错:malformed input off : 50, length : 1

Execution failed for task :app:mapǧ&#xfffd;&#xfffd;Ѫսƪ_officialOfficialReleaseSourceSetPaths. > Could not resolve all files for configuration :app:ǧ&#xfffd;&#xfffd;Ѫսƪ_officialOfficialReleaseRuntimeClasspath. > Failed to trans…

[云] 大数据分析栈(Big Data Analytics Stack)+ Apache Hadoop分布式文件系统(HDFS)+Apache Spark

任务概述 本次作业旨在帮助你理解大数据分析栈&#xff08;Big Data Analytics Stack&#xff09;的工作原理&#xff0c;并通过实际操作加深认识。你将搭建Apache Hadoop分布式文件系统&#xff08;HDFS&#xff09;作为底层文件系统&#xff0c;并将Apache Spark作为执行引擎…

ESP8266 自定义固件烧录-Tcpsocket固件

一、固件介绍 固件为自定义开发的一个适配物联网项目的开源固件&#xff0c;支持网页配网、支持网页tcpsocket服务器配置、支持串口波特率设置。 方便、快捷、稳定&#xff01; 二、烧录说明 固件及工具打包下载地址&#xff1a; https://download.csdn.net/download/flyai…

新能源汽车空调压缩机:科技驱动的冷暖核心

一、新能源汽车空调系统概述 新能源汽车空调系统在车辆中起着至关重要的作用&#xff0c;它直接影响着驾乘人员的舒适度。新能源汽车空调系统主要由制冷系统、加热系统、送风系统、操纵控制系统和空气净化系统等组成。 制冷系统通常由电动压缩机、冷凝器、压力传感器、电子膨…

Leetcode 213. 打家劫舍 II 动态规划

原题链接&#xff1a;Leetcode 213. 打家劫舍 II class Solution { public:int rob(vector<int>& nums) {int n nums.size();if (n 1)return nums[0];if (n 2)return max(nums[0], nums[1]);// 如果偷了第一家&#xff0c;就不能偷最后一家int dp[n - 1];dp[0] …

助力AI智能化时代:全国产化飞腾FT2000+/64+昇腾310B服务器主板

在信息技术快速发展的今天&#xff0c;服务器作为数据处理和存储的核心设备&#xff0c;肩负着越来越重要的使命。全国产化的服务器主板&#xff0c;采用飞腾FT2000/64核处理器&#xff0c;搭配华为昇腾310的AI芯片&#xff0c;提供卓越的性能与可靠性。 核心配置&#xff0c;强…

IO 多路复用技术:原理、类型及 Go 实现

文章目录 1. 引言IO 多路复用的应用场景与重要性高并发下的 IO 处理挑战 2. IO 多路复用概述什么是 IO 多路复用IO 多路复用的优点与适用场景 3. IO 多路复用的三种主要实现3.1 select3.2 poll3.3 epoll三者对比 4. 深入理解 epoll4.1 epoll 的三大操作4.2 epoll 的核心数据结构…

大学英语神器:让GPT帮助你攻克完型填空和阅读理解

这里写目录标题 0、前言一、再来十篇完型填空和阅读理解第一部分&#xff1a;操作指南1.访问链接&#xff1a;ChatGPT 4o国内直接访问地址&#xff1a;https://share.xuzhugpt.cloud/2.上plus的车 第二部分&#xff1a;实操展示①完型填空②阅读理解 二、用户体验 0、前言 学习…

masm汇编debug调试1~100求和

计算123...100并把结果放到寄存器AX里 assume cs:codecode segment start:mov ax,0mov cx,100 s:add ax,cxloop smov ax,4c00hint 21hcode ends end start 效果演示&#xff1a;

LeetCode3226题. 使两个整数相等的位更改次数(原创)

【题目描述】 给你两个正整数 n 和 k。 你可以选择 n 的 二进制表示 中任意一个值为 1 的位&#xff0c;并将其改为 0。 返回使得 n 等于 k 所需要的更改次数。如果无法实现&#xff0c;返回 -1。 示例 1&#xff1a; 输入&#xff1a; n 13, k 4 输出&#xff1a; 2 解释&am…

ubuntu 异常 断电 日志 查看

sudo less /var/log/syslog 搜 Linux version

Python:入门基础

目录 常量和表达式 变量 变量的语法 变量的类型 动态类型特性 注释的使用 输入和输出 通过控制台输出 通过控制台输入 运算符 算术运算符 关系运算符 逻辑运算符 赋值运算符 常量和表达式 print是Python中的一个内置函数&#xff0c;使用print函数可以将数据打印…

ChatGPT 高级语音模式已登陆 Windows 和 Mac 平台,对话更自然

OpenAI ChatGPT 高级语音模式已登陆 Windows 和 Mac 平台&#xff0c;对话更自然&#xff0c;拟态更逼真 OpenAI 于10月31日正式宣布&#xff0c;ChatGPT 的高级语音模式&#xff08;Advanced Voice Mode&#xff0c;简称 AVM&#xff09;现已登陆 Windows 和 Mac 平台。基于最…

【深度学习】InstantIR:图片高清化修复

InstantIR——借助即时生成参考的盲图像修复新方法 作者:Jen-Yuan Huang 等 近年来,随着深度学习和计算机视觉技术的飞速发展,图像修复技术取得了令人瞩目的进步。然而,对于未知或复杂退化的图像进行修复,仍然是一个充满挑战的任务。针对这一难题,研究者们提出了 Insta…