首页 > 其他分享 >FastAPI-4:异步、并发和Starlette

FastAPI-4:异步、并发和Starlette

时间:2024-06-07 15:46:06浏览次数:31  
标签:异步 ... Python FastAPI 线程 async Starlette

4 异步、并发和Starlette

本章关注FastAPI的底层Starlette库,尤其是它对异步处理的支持。在概述了Python中“同时做更多事情”的多种方法后,您将看到Python中较新的async和await关键字是如何融入Starlette和FastAPI的。

4.1 Starlette

FastAPI 的大部分网络代码都基于 Tom Christie 创建的 Starlette 软件包。它既可以作为网络框架单独使用,也可以作为其他框架(如 FastAPI)的库使用。与其他网络框架一样,Starlette处理所有常见的HTTP请求解析和响应生成。它类似于Flask的基础软件包Werkzeug。

但它最重要的功能是支持现代Python异步网络标准: ASGI。到目前为止,大多数 Python 网络框架(如 Flask 和 Django)都是基于传统的同步WSGI标准。由于网络应用程序经常连接到速度慢得多的代码(如数据库、文件和网络访问),ASGI避免了基于WSGI应用程序的阻塞和繁忙等待。

因此,Starlette和使用它的框架是速度最快的Python Web包,甚至可与Go和Node.js应用程序相媲美。

4.2 并发类型

在并行计算中,任务会同时分布在多个专用CPU上。这在图形和机器学习等“数字计算”应用中很常见。

在并发计算中,每个CPU在多个任务之间切换。有些任务比其他任务耗时更长,我们希望缩短所需的总时间。读取文件或访问远程网络服务要比在CPU中运行计算慢数千到数百万倍。

网络应用程序就承担了大量这样的慢速工作。如何让网络服务器或任何服务器运行得更快?本节将讨论从全系统到本章重点的一些可能性:FastAPI对Python的async和await的实现。

4.2.1 分布式和并行计算

大的应用程序--在单个 CPU 上运行会非常吃力--您可以将它分解成多个部分,让这些部分在单台机器或多台机器的不同CPU上运行。这样做的方法有很多很多,如果你有这样一个应用程序,你已经知道了其中的一些方法。管理所有这些部分比管理单个服务器更加复杂和昂贵。

我们重点关注的是可以安装在单个服务器上的中小型应用程序。这些应用程序可以混合使用同步和异步代码,由FastAPI进行很好的管理。

4.2.2 操作系统进程

操作系统调度资源:内存、CPU、设备、网络等。操作系统运行的每个程序都在一个或多个进程中执行代码。操作系统为每个进程提供受管理、受保护的资源访问权限,包括它们何时可以使用CPU。

大多数系统使用抢占式进程调度,不允许任何进程占用CPU、内存或其他资源。操作系统会根据其设计和设置不断暂停和恢复进程。

对于开发人员来说,好消息是:这不是你的问题!但坏消息(通常似乎与好消息如影随形)是:即使你想改变,也无能为力。

对于CPU密集型的Python应用程序,通常的解决方案是使用多个进程,让操作系统来管理它们。Python 为此提供了multiprocessing模块。

4.2.3 操作系统线程

可以在单个进程中运行控制线程。Python 的threading可以管理这些线程。

当程序受I/O约束时,通常建议使用线程,而当程序受CPU约束时,则建议使用多进程。但线程编程起来很棘手,可能会导致难以发现的错误。

传统上,Python将基于进程的库和基于线程的库分开。开发人员必须学习其中任何一个库的神秘细节才能使用它们。最近一个名为 concurrent.futures 的软件包提供了更高级别的接口,使它们更容易使用。

正如您将看到的,使用较新的异步函数,您可以更轻松地获得线程的优势。FastAPI 还通过线程池管理普通同步函数(def,而非 async def)的线程。

4.2.4 Green线程(协程)

绿色线程(如 greenlet、gevent 和 Eventlet)是一种更为神秘的机制。这些线程是合作式的(不是抢占式的)。它们类似于操作系统线程,但运行在用户空间(即你的程序)而非操作系统内核。它们通过对标准Python函数进行 “猴子修补”(在运行过程中修改标准 Python 函数),使并发代码看起来像正常的顺序代码:当它们阻塞等待I/O时,就会放弃控制。

操作系统线程比操作系统进程更“轻”(使用更少内存),而绿色线程比操作系统线程更轻。在某些基准测试中,所有异步方法通常都比同步方法快。

你可能会想知道gevent和asyncio孰优孰劣?我认为并没有一个适用于所有用途的单一偏好。协程是在前面实现的(使用了多人游戏 Eve Online 的思想)。本书以Python的标准asyncio为特色,FastAPI使用的asyncio比线程更简单,性能也很好。

4.2.5 回调(Callbacks)

游戏和图形用户界面等交互应用程序的开发人员可能对回调并不陌生。您可以编写函数,并将它们与鼠标点击、按键或时间等事件关联起来。这类Python软件包中的佼佼者是Twisted。它的名字反映了这样一个现实:基于回调的程序有点 “内向外”,很难跟上。

4.2.6 Python生成器(Generators)

在Python生成器中,您可以从任意点停止并返回,然后再回到该点。其中的诀窍就是yield关键字。

让我们看一个简单的for循环:

In [1]: def doh():
   ...:     return ["Homer: D'oh!", "Marge: A deer!", "Lisa: A female deer!"]
   ...: 

In [2]: for line in doh():
   ...:     print(line)
   ...: 
Homer: D'oh!
Marge: A deer!
Lisa: A female deer!

当列表相对较小的时候,这种方法非常有效。列表会占用内存, 可以改用生成器:

In [3]: def doh2():
   ...:     yield "Homer: D'oh!"
   ...:     yield "Marge: A deer!"
   ...:     yield "Lisa: A female deer!"
   ...: 

In [4]: for line in doh2():
   ...:     print(line)
   ...: 
Homer: D'oh!
Marge: A deer!
Lisa: A female deer!

我们迭代的不是普通函数 doh() 返回的列表,而是生成器函数 doh2() 返回的生成器对象。实际的迭代(for...in) 看起来是一样的。Python 会从 doh2() 返回第一个字符串,但会在下一次迭代时跟踪它的位置,以此类推,直到函数用完对话为止。

任何包含yield的函数都是生成器函数,它能返回函数并恢复执行的能力。

4.2.7 Python async、await 和 asyncio

import time

def q():
    print("Why can't programmers tell jokes?")
    time.sleep(3)

def a():
    print("Timing!")

def main():
    q()
    a()

main()

上面程序在打印"Why can't programmers tell jokes?"后会停顿3s。

async实例

In [5]: import asyncio

In [6]: async def q():
   ...:     print("Why can't programmers tell jokes?")
   ...:     await asyncio.sleep(3)
   ...: 

In [7]: async def a():
   ...:     print("Timing!")
   ...: 

In [8]: async def main():
   ...:     await asyncio.gather(q(), a())
   ...: 

In [9]: asyncio.run(main())
Why can't programmers tell jokes?
Timing!
  • 执行q()
  • 已经设置了秒表,三秒钟后回来
  • 同时运行 a(),立即打印
  • 没有其他等待,所以回到 q()。
  • 无聊的事件循环!我会坐在这里盯着剩下的三秒钟。
  • 好的,现在我完成了。

asyncio.sleep()用于需要一定时间的函数,就像读取文件或访问网站的函数一样。你将await放在可能花费大部分时间等待的函数前面。该函数需要在其def前加上async。

调用者必须在调用该函数前加上await。调用者本身也必须声明 async def,而且调用者必须一直等待它。

顺便说一句,即使一个函数不包含对另一个异步函数的await调用,你也可以将它声明为异步函数。这样做也无妨。

参考资料

4.3 FastAPI和异步

由于网络服务器需要花费大量时间等待,因此可以通过避免部分等待来提高性能,换句话说,就是并发。其他网络服务器使用了许多前面提到的方法:线程、gevent 等。FastAPI是速度最快的 Python Web 框架之一,其中一个原因就是它通过底层Starlette包的ASGI支持和一些自己的发明,将异步代码融入其中。

使用async和await本身并不会使代码运行得更快。事实上由于async设置的开销,运行速度可能会稍慢一些。async 的主要用途是避免I/O的长时间等待。

现在,让我们看看之前的网络端点调用,看看如何让它们成为异步。

from fastapi import FastAPI
import asyncio

app = FastAPI()

@app.get("/hi")
async def greet():
    await asyncio.sleep(1)
    return "Hello? World?"

运行:

$ uvicorn greet_async:app

第二种方法:

from fastapi import FastAPI
import asyncio
import uvicorn

app = FastAPI()

@app.get("/hi")
async def greet():
    await asyncio.sleep(1)
    return "Hello? World?"
    
if __name__ == "__main__":
    uvicorn.run("greet_async_uvicorn:app")

FastAPI 在接收到 URL /hi 的 GET 请求时,会自行调用 async greet() 路径函数。您不需要在任何地方添加 await。但对于您定义的任何其他异步定义函数,调用者必须在每次调用前添加await。

FastAPI 运行异步事件循环来协调异步路径函数,并为同步路径函数运行一个线程池。开发人员不需要知道这些棘手的细节,这是一大优点。例如,您不需要运行asyncio.gather() 或 asyncio.run() 等方法,就像前面的(独立、非 FastAPI)笑话示例一样。

4.4 直接使用Starlette

FastAPI并不像Pydantic那样公开Starlette。Starlette 在很大程度上是引擎室中嗡嗡作响的机器,它能保证船只平稳运行。

不过,如果您好奇,可以直接使用Starlette编写网络应用程序:

from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route

async def greeting(request):
    return JSONResponse('Hello? World?')

app = Starlette(debug=True, routes=[
    Route('/hi', greeting),
])

运行此网络应用

$ uvicorn starlette_hello:app

在我看来,FastAPI的添加使得网络API的开发变得更加容易。

4.5 小结

在概述了提高并发性的方法后,本章扩展了使用最新Python关键字async和await的函数。它展示了FastAPI和Starlette如何处理普通的同步函数和这些新的异步函数。

标签:异步,...,Python,FastAPI,线程,async,Starlette
From: https://www.cnblogs.com/testing-/p/18235497

相关文章

  • springboot-异步使用
    创建配置类,开启异步和创建线程packagecom.chulx.demo1.config;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.scheduling.annotation.EnableAsync;importorg.springframe......
  • python系列:FASTAPI系列 04-GET请求 params参数
    FASTAPI系列04-GET请求params参数FASTAPI系列04-GET请求params参数前言一、查询参数二、参数的默认值三、多路径查询参数四、查询参数为必填项总结FASTAPI系列04-GET请求params参数前言get请求的参数在url后面携带,通常称做queryparams一、查询参数在......
  • FastAPI-3:快速入门
    3快速入门第二章是python基础,故不做介绍。FastAPI是一个现代、快速(高性能)的网络框架,用于使用基于标准Python类型提示的Python3.6+构建API。FastAPI的创建者是SebastiánRamírez。FastAPI由SebastiánRamírez于2018年发布。与大多数PythonWeb框架相比,它在很多方面都更......
  • 记录--localStorage是同步还是异步的?为什么?
    ......
  • validate方法进行表单异步校验时,回调函数内部避免使用全局变量
    对整个表单进行校验的方法,参数为一个回调函数。该回调函数会在校验结束后被调用,并传入两个参数:是否校验成功和未通过校验的字段。若不传入回调函数,则会返回一个promise问题代码:save(){console.log(that.pos.indexName)console.log(that.pos.indexCode)......
  • FastAPI-1:现代网络
    1现代网络曾几何时,网络小而简单。开发者们把PHP、HTML和MySQL调用放到一个文件中,然后自豪地告诉大家去看看他们的网站,这样做非常有趣。但随着时间的推移,网络发展到了数以亿计,不对,是数以万亿计的页面,早期的游乐场变成了主题公园的元宇宙。在本章中,我将指出一些与现代网络日......
  • python系列:一文读懂FastAPI:Python 开发者的福音
    一文读懂FastAPI:Python开发者的福音一文读懂FastAPI:Python开发者的福音第一步:安装FastAPI第二步:创建一个FastAPI应用第三步:定义接口第四步:运行应用第五步:查看接口文档总结一文读懂FastAPI:Python开发者的福音FastAPI是一个基于Python的现代化Web框架,它提供了快......
  • 操作系统基本特性:并发、共享、虚拟、异步
    目录一.并发1.并发的优势2.并发的实现3.并发的应用场景4.并发的挑战二.共享1.共享的优势2.共享资源的实现机制3.进程同步和互斥4.避免冲突和死锁5.实例分析文件共享内存共享设备共享三.虚拟1.虚拟技术的优势2.虚拟化技术的主要实现3.实例分析虚拟内存虚拟......
  • 【前端每日基础】day42——关于 Vuex 共有几个属性,哪里可以书写同步任务,哪里可以书写
    Vuex是Vue.js的一个状态管理模式,它集中式地存储和管理应用的所有组件的状态。Vuex提供了以下几个核心属性,每个属性在状态管理中有不同的用途:Vuex共有的几个属性:State:用于存储应用的状态。可以通过this.$store.state或在组件中通过mapState辅助函数访问。Gette......
  • UniTask入门指南:简化Unity中的异步编程
    UniTask入门指南:简化Unity中的异步编程介绍:UniTask是一个轻量级、高性能的异步编程库,专门针对Unity开发进行了优化。与Unity标准的Task系统相比,UniTask提供了更加简洁和高效的异步编程方式。在Unity项目中使用UniTask可以大大提高开发效率,简化异步操作的编码过程。UniTask......