FastAPI教程——并发async/await

本文参考FastAPI教程https://fastapi.tiangolo.com/zh/tutorial

并发async/await

有关路径操作函数的async def语法以及异步代码、并发和并行的一些背景知识。

  • 通过async def声明你的路径操作函数:
@app.get('/')
async def read_results():
    results = await some_library()
    return results
  • 正常情况的def声明
@app.get('/')
def results():
    results = some_library()
    return results

注意:你可以根据需要在路径操作函数中混合使用defasync def,并使用最适合你的方式去定义每个函数。FastAPI将为他们做正确的事情。无论如何,在上述任何情况下,FastAPI仍将异步工作,速度也非常快。但是,通过遵循上述步骤,它将能够进行一些性能优化。

技术细节

Python的现代版本支持通过一种叫"协程"——使用asyncawait语法的东西来写”异步代码“。

让我们在下面的部分中逐一介绍:

  • 异步代码
  • asyncawait
  • 协程

异步代码

异步代码仅仅意味着编程语言有办法告诉计算机/程序在代码中的某个点,它将不得不等待在某些地方完成一些事情。让我们假设一些事情被称为”慢文件“。

所以,在等待”慢文件“完成的这段时间,计算机可以做一些其他工作。

然后计算机/程序每次有机会都会回来,因为它又在等待,或者它完成了当前所有的工作。而且它将查看它等待的所有任务中是否有已经完成的,做它必须做的事情。

接下来,它完成第一个任务(比如是我们的”慢文件“)并继续与之相关的一切。

这个”等待其他事情“通常指的是一些相对较慢(与处理器和RAM存储器的速度相比)的I/O操作,比如说:

  • 通过网络发送来自客户端的数据
  • 客户端接收来自网络中的数据
  • 磁盘中要由系统读取并提供给程序的文件的内容
  • 程序提供给系统的要写入磁盘的内容
  • 一个API的远程调用
  • 一个数据库操作,直到完成
  • 一个数据库查询,直到返回结果
  • 等等

这个执行的时间大多是在等待I/O操作,因此它们被叫做”I/O密集型“操作。

它被称为”异步“的原因是因为计算机/程序不必与慢任务”同步“,去等待任务完成的确切时刻,而在此期间不做任何事情直到能够获取任务结果才继续工作。

相反,作为一个”异步“系统,一旦完成,任务就可以排队等待一段时间(几微秒),等待计算机程序完成它要做的任何事情,然后回来获取结果并继续处理它们。

对于”同步“,它们通常也使用”顺序“一词,因为计算机程序在切换到另一个任务之前是按顺序执行所有步骤,即使这些步骤涉及到等待。

并发和并行

上述异步代码的思想有时也被称为”并发“,它不同于”并行“。

  • 大多数Web应用有很多等待场景,因此并发系统更有意义,这种异步机制正是NodeJS受到欢迎的原因(尽管NodeJS不是并行的),以及Go作为编程语言的优势所在。
  • 在大多数执行时间是由实际工作(而不是等待)占用的,并且计算机中的工作是由CPU完成的,这种”CPU 密集型“操作的情况,并行是必要的,例如,音频或图像处理,计算机视觉,机器学习,深度学习,这些场景都需要复杂的数学处理。

并发+并行:Web+机器学习

使用FastAPI,您可以利用Web开发中常见的并发机制的优势(NodeJS的主要吸引力)。

并且,您也可以利用并行和多进程(让多个进程并行运行)的优点来处理与机器学习系统中类似的CPU密集型工作。

这一点,再加上Python是数据科学、机器学习(尤其是深度学习)的主要语言这一简单事实,使得FastAPI与数据科学/机器学习Web API和应用程序(以及其他许多应用程序)非常匹配。

了解如何在生产环境中实现这种并行性,可查看此文Deployment

asyncawait

现代版本的Python有一种非常直观的方式来定义异步代码。这使它看起来就像正常的”顺序“代码,并在适当的时候”等待“。

当有一个操作需要等待才能给出结果,且支持这个新的Python特性时,您可以编写如下代码:

burgers = await get_burgers(2)

这里的关键是await。它告诉Python它必须等待get_burgers(2)完成它的工作,然后将结果存储在burgers中。这样,Python就会知道此时它可以去做其他事情(比如接收另一个请求)。

要使await工作,它必须位于支持这种异步机制的函数内。因此,只需使用async def声明它:

async def get_burgers(number: int):
    # Do some asynchronous stuff to create the burgers
    return burgers

而不是def

# This is not asynchronous
def get_sequential_burgers(number: int):
    # Do some sequential stuff to create the burgers
    return burgers

使用async def,Python就知道在该函数中,它将遇上await,并且它可以”暂停“执行该函数,直至执行其他操作后回来。

当你想调用一个async def函数时,你必须”等待“它。因此,下面的代码不会起作用:

# This won't work, because get_burgers was defined with: async def
burgers = get_burgers(2)

因此,如果您使用的库告诉您可以使用await调用它,则需要使用async def创建路径操作函数,如:

@app.get('/burgers')
async def read_burgers():
    burgers = await get_burgers(2)
    return burgers

更多技术细节

您可能已经注意到,await只能在async def定义的函数内部使用。

但与此同时,必须”等待“通过async def定义的函数。因此,带async def的函数也只能在async def定义的函数内部调用。

那么,这关于先有鸡还是先有蛋的问题,如何调用第一个async函数?

如果您使用FastAPI,你不必担心这一点,因为”第一个“函数将是你的路径操作函数,FastAPI将知道如何做正确的事情。

但如果您想在没有FastAPI的情况下使用async/await,则可以这样做。

编写自己的异步代码

Starlette(和FastAPI)是基于AnyIO,这使得它们可以兼容Python的标准库asyncioTrio

特别是,你可以直接使用AnyIO来处理高级的并发用例,这些用例需要在自己的代码中使用更高级的模式。

即使您没有使用FastAPI,您也可以使用AnyIO编写自己的异步程序,使其拥有较高的兼容性并获得一些好处(例如,结构化并发)。

协程

协程只是async def函数返回的一个非常奇特的东西的称呼。Python知道它有点像一个函数,它可以启动,也会在某个时刻结束,而且它可能会在内部暂停,只要内部有一个await

通过使用asyncawait的异步代码的所有功能大多数被概括为”协程“。它可以与Go的主要关键特性”Goroutines“相媲美。

  • 47
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值