FastApi教程|并发和异步/等待

发表时间:2020-03-09

有关 路径操作功能 async def 语法的 详细信息, 以及有关异步代码,并发性和并行性的一些背景知识。

匆忙?

TL; DR:

如果您使用的第三方库告诉您使用进行调用 await ,例如:

results = await some_library()

然后,声明你的 路径操作功能 async def ,如:

@app.get('/')
async def read_results():
    results = await some_library()
    return results

注意

您只能在用 await 创建的函数内部使用 async def


如果您使用的是与某些东西(数据库,API,文件系统等)通信的第三方库,并且不支持使用 await ,(大多数数据库库目前都是这种情况),那么请声明您的 路径操作 正常运行,只有 def ,如:

@app.get('/')
def results():
    results = some_library()
    return results

如果您的应用程序(以某种方式)不必与其他任何东西进行通信并等待其响应,请使用 async def


如果您不知道,请使用normal def


注意 :您可以根据需要混合 def async def 路径中使用各种功能 ,并使用最适合的选项定义每个 功能 。 FastAPI将对他们做正确的事。

无论如何,在以上任何情况下,FastAPI仍将异步运行并且非常快。

但是通过执行上述步骤,它将能够进行一些性能优化。

技术细节

现代版本的Python 使用称为 “协程”的 with 语法 支持 “异步代码” async await

让我们在下面的部分中逐个查看该短语:

  • 异步代码
  • async await
  • 协程

异步代码

异步代码只是意味着该语言可以告诉计算机/程序,在代码中的某个点上,他将不得不等待 其他事情在其他地方 完成。 假设 其他 名称称为“慢文件”。

因此,在此期间,“慢文件”完成时,计算机可以继续执行其他一些工作。

然后,计算机/程序每次有机会都会回来,因为它再次等待,或者只要他完成了那时的所有工作。 并且它将查看他正在等待的任何任务是否已经完成了必须要做的事情。

然后,它需要完成第一个任务(例如,我们的“慢文件”),并继续执行与该任务有关的所有操作。

“等待其他东西”通常是指 相对“慢”的 I / O 操作(与处理器和RAM内存的速度相比),例如等待:

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

由于执行时间主要是通过等待 I / O 操作 而消耗的 ,因此它们将它们称为“ I / O绑定”。

之所以称为“异步”,是因为计算机/程序不必与速度较慢的任务“同步”,而是等待任务完成的确切时刻,而无所事事,才能够获取任务结果并继续工作。

取而代之的是,通过成为“异步”系统,一旦完成,任务可以排队等待一点(几微秒),以使计算机/程序完成其要做的一切,然后返回以获取结果并继续与他们合作。

对于“同步”(与“异步”相反),它们通常也使用术语“顺序”,因为计算机/程序在切换到另一任务之前会按顺序执行所有步骤,即使这些步骤涉及等待。

并发和 Burgers

上述 异步 代码的 想法 有时也称为 “并发” 。 它与 “并行性” 不同 。

并发性 并行性 都与“不同的事物或多或少同时发生”有关。

但是 并发 并行 之间的细节 却大不相同。

要了解差异,请想象以下关于汉堡的故事:

并发汉堡

您可以尽情享受快餐,在排队时收银员会从您面前的人那里接单。

然后轮到您了,您下令订购两个非常特别的汉堡供您品尝。

你付钱。

收银员对厨房里的那个人说了些什么,所以他知道他必须准备您的汉堡(即使他目前正在为以前的顾客准备汉堡)。

收银员会给你您的回合号码。

在等待时,您可以随便吃点东西,拿起桌子,坐下来与您的暗恋对象聊天很长时间(因为您的汉堡非常花哨,需要一些时间来准备)。

当您在暗恋中坐在桌子上时,您在等待汉堡的同时,可以花时间欣赏自己的暗恋是多么的棒,可爱和聪明。

在等待和暗恋对象交谈时,您不时检查柜台上显示的数字,看看是否已经轮到您了。

然后到了某个时候,轮到您了。 您去柜台,买汉堡,再回到餐桌。

您和您的暗恋者吃了汉堡,度过了愉快的时光。


假设您是那个故事中的计算机/程序。

当您在排队时,您只是闲着,等待轮到您,而没有做任何“富有成效的”事情。 但是生产线很快,因为收银员只接订单,所以很好。

然后,轮到您来进行实际的“生产性”工作,处理菜单,确定所需内容,选择迷恋者,付款,检查是否提供了正确的账单或卡,检查是否正确收取了费用,订单中包含正确的商品,等等。

但是,即使您仍然没有汉堡,但您与收银员的工作却“暂停”,因为您必须等待汉堡准备就绪。

但是,当您离开柜台并坐在桌子上并带有转弯数字时,您可以将注意力转移到暗恋对象上,并“专心”于此。 然后,您又在做一些非常“富有成效”的事情,就像暗恋中的调情一样。

然后,收银员将您的号码放在柜台显示屏上说“我已经做好了汉堡的准备”,但是当显示的号码更改为您的转机号码时,您不会立即跳起来。 您知道没有人会偷汉堡,因为您有轮到的机会,而且有他们的。

因此,您等待暗恋结束故事(完成当前正在处理的工作/任务),轻轻地笑着说要去吃汉堡。

然后,您去柜台,完成现在已经完成的初始任务,挑选汉堡,说声谢谢,然后把它们送到餐桌上。 至此,完成了与柜台互动的步骤/任务。 这样就创建了一个新任务,即“吃汉堡”,但是上一个“获取汉堡”已经完成。

并行汉堡

您随心所欲地获得平行快餐。

您排队时,有几个(假设是8个)收银员接您面前的人的命令。

在您等待汉堡包准备好之前,每个人都必须离开柜台,因为8个收银员中的每个人都会自己准备汉堡包,然后再下一个订单。

然后轮到您了,您下单了两个非常特别的汉堡供您品尝。

你付钱。

收银员去厨房。

您等待着,站在柜台前,因为没有轮到的号码,所以没有人可以在您之前买汉堡。

由于您和您的暗恋对象忙于不让任何人站在您面前并在汉堡到来时拿走他们的汉堡,因此您无法注意自己的暗恋对象。

这是“同步”工作,您与收银员/厨师“同步”。 您必须等待,并在收银员/厨师将汉堡包完成并将其交给您的确切时间到那里,否则,其他人可能会带走它们。

然后,您的收银员/厨师终于在柜台前等待了很长时间之后,又带着汉堡回来了。

您拿起汉堡,暗恋到桌子旁。

您只要吃掉它们,就完成了。

由于大部分时间都花在柜台前等待,所以没有太多的谈话或调情。


在并行汉堡的这种情况下,您是一台具有两个处理器(您和您的迷恋者)的计算机/程序,它们都在等待很长时间,并且将他们的注意力放在“等待柜台”上。

该快餐店有8个处理机(收银员/厨师)。 虽然同时存在的汉堡店可能只有2个(一名收银员和一名厨师)。

但是,最终的体验并不是最好的。


这将是汉堡的平行故事。

对于一个更“现实生活”的例子,想象一下一家银行。

直到最近,大多数银行都设有多个收银员和一大笔钱。

所有收银员与一个客户接一个地处理所有工作。

而且您必须排队等候很长时间,否则您会掉头。

您可能不希望随心所欲地在银行办事。

汉堡结论

在“快餐汉堡让您沉迷”的情况下,由于等待很多,因此并发系统更为有意义。

大多数Web应用程序都是这种情况。

很多很多用户,但是您的服务器正在等待不太好的连接来发送请求。

然后再次等待响应返回。

这种“等待”以微秒为单位,但是总的来说,最后还有很多等待。

这就是为什么将异步代码用于Web API非常有意义。

大多数现有的流行Python框架(包括Flask和Django)都是在Python中新的异步功能存在之前创建的。 因此,它们的部署方式支持并行执行和不如新功能强大的旧形式的异步执行。

即使异步Web Python(ASGI)的主要规范是在Django上开发的,也可以添加对WebSockets的支持。

这种异步性使NodeJS变得流行(即使NodeJS不是并行的),这就是Go作为一种编程语言的优势。

这与您使用 FastAPI 获得的性能水平相同 。

并且由于可以同时具有并行性和异步性,因此与大多数经过测试的NodeJS框架相比,您可以获得更高的性能,并且可以与Go(后者是一种更接近C的编译语言)相提并论 (这要归功于Starlette)

并发比并行更好吗?

不! 这不是故事的寓意。

并发不同于并行。 在 涉及大量等待的 特定 情况下, 它更好 。 因此,对于Web应用程序开发,它通常比并行性好得多。 但并非一切。

因此,为平衡起见,请想象以下简短故事:

您必须打扫一个大而肮脏的房子。

是的,这就是整个故事


在房子的多个地方,无处等待,只需完成大量工作即可。

您可以像汉堡示例中那样先进行转弯,首先是起居室,然后是厨房,但是由于您没有等待任何事情,只是打扫卫生,所以转弯不会影响任何事情。

轮流或不轮流(并发)花费的时间相同,并且您将完成相同的工作量。

但是在这种情况下,如果您可以带8个前收银员/厨师/现职清洁工,并且每个人(加上您)可以占用房子的一部分区域进行清洁,则可以 并行 完成所有工作 ,在额外的帮助下,很快就可以完成。

在这种情况下,每个清洁员(包括您)都将成为处理器,完成他们的工作。

由于大部分执行时间都由实际工作(而不是等待)花费,而计算机中的工作由 CPU 完成 ,因此他们将这些问题称为“ CPU绑定”。


CPU绑定操作的常见示例是需要复杂数学处理的事物。

例如:

  • 音频 图像处理
  • 计算机视觉 :图像由数百万个像素组成,每个像素具有3种值/颜色,通常需要同时对这些像素进行运算的处理)
  • 机器学习 :通常需要大量的“矩阵”和“向量”乘法。 想一想一个巨大的电子表格,其中包含数字并将所有数字同时相乘。
  • 深度学习 :这是机器学习的一个子领域,因此同样适用。 只是没有一个要相乘的数字电子表格,而是大量的电子表格,在许多情况下,您使用特殊的处理器来构建和/或使用那些模型。

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

借助 FastAPI, 您可以利用Web开发中非常常见的并发优势( 与 NodeJS 相同的主要吸引力)。

但是,对于 像机器学习系统那样的 CPU受限 工作负载, 您也可以利用并行和多处理(具有多个并行运行的进程)的好处 。

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

要了解如何在生产中实现这种并行性,请参阅关于 部署 的部分 。

async await

现代版本的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 ,则不必担心,因为该“第一个”函数将是您的 path操作函数 ,而FastAPI将知道如何做正确的事情。

但是,如果要使用 async / await 不 使用 FastAPI, 请查看官方Python文档

其他形式的异步代码

采用这种风格 async ,并 await 在语言相对较新。

但这使使用异步代码变得容易得多。

最近,现代版本的JavaScript(在Browser和NodeJS中)也包含了相同的语法(或几乎相同)。

但是在此之前,处理异步代码非常复杂和困难。

在以前的Python版本中,您可能使用过thread或 Gevent 。 但是,代码在理解,调试和思考方面更加复杂。

在早期版本的NodeJS /浏览器JavaScript中,您将使用“回调”。 从而导致 回调地狱

协程

协程 只是 async def 函数 返回的事物的非常 恰当的 术语 。 Python知道它可以像一个函数一样开始并且在某个时刻结束,但是只要其中有一个 await 内部,它 也可能在内部暂停 。

但是,与 async 和 一起使用异步代码的所有这些功能 await 被概括为使用“协程”。 它可与Go的主要关键功能“ Goroutines”相媲美。

结论

让我们从上方看相同的短语:

现代版本的Python 使用称为 “协程”的 with 语法 支持 “异步代码” async await

现在应该更有意义了。

这一切(通过Starlette)为FastAPI增添了力量,并使其具有如此出色的性能。

技术细节

警告

您可能可以跳过此步骤。

这些是 FastAPI 如何 在下面工作的 非常技术性的细节 。

如果您具有一定的技术知识(协同例程,线程,阻塞等),并且对FastAPI如何处理 async def vs正常 感到好奇 def ,请继续。

路径操作功能

当 使用normal 而不是 声明 路径操作函数 时 ,该 函数 在外部线程池中运行,然后等待该线程,而不是直接调用它(因为它将阻塞服务器)。 def async def

如果您来自另一个无法按照上述方式工作的异步框架,并且习惯于使用琐碎的 性能 定义 纯 平凡的仅计算 路径操作功能, def 获取微小的性能提升(大约100纳秒),请注意,在 FastAPI中 ,效果相反。 在这些情况下, async def 除非 路径操作函数 使用执行阻塞 IO的 代码, 否则 最好使用 。

不过,在这两种情况下,有机会, FastAPI 仍然快 于(或至少可比)以前的框架。

依赖

依赖关系也是如此。 如果依赖项是标准 def 函数而不是 async def ,则它在外部线程池中运行。

分依赖

您可以具有多个相互依赖的依赖项和子依赖项(作为函数定义的参数),其中一些依赖项可以使用创建, async def 而 另 一些可以使用normal 创建 def 。 它仍然可以工作,并且使用normal创建 def 的线程将在外部线程上调用,而不是“等待”。

其它实用功能

您直接调用的任何其他实用程序函数都可以使用normal def async def FastAPI 创建, 并且FastAPI不会影响您的调用方式。

这与FastAPI为您提供的功能相反: 路径操作功能 和相关性。

如果您的实用程序函数是带有的普通函数 def ,则将直接调用它(在您将其写入代码时),而不是在线程池中调用,如果该函数是使用创建的, async def 则应在代码中调用该函数时等待该函数。


同样,这些都是非常技术性的细节,如果您要搜索它们,可能会很有用。

否则,您应该符合上一节中的指南: 急吗?

文章来源互联网,如有侵权,请联系管理员删除。邮箱:417803890@qq.com / QQ:417803890

微配音

Python Free

邮箱:417803890@qq.com
QQ:417803890

皖ICP备19001818号
© 2019 copyright www.pythonf.cn - All rights reserved

微信扫一扫关注公众号:

联系方式

Python Free