Python FastAPI 异步变量共享
Python FastAPI Async Variable Sharing
如果我有以下代码,变量 service
将如何影响端点的异步性质?变量会被共享吗?或者它会在使用时被锁定,从而阻止其他端点访问它,直到当前端点完成为止?
我假设服务实例是无状态的,即如果我在每个端点中创建一个服务实例,那将是等效的。我不太愿意这样做,因为我不知道哪个更耗时,实例化和销毁一个Service对象还是共享一个?
from typing import List, Union
from fastapi import APIRouter, Body, Depends
# Service definition
router = APIRouter()
service = Service()
@router.post("/a", response_model=Union[A, None])
async def x():
service.start()
pass
@router.post("/b", response_model=Union[B, None])
async def y():
service.stop()
pass
How would the variable service affect the asynchronous nature of the endpoints?
首先,如果您的 service.stop()
不是协程,asyncio 不会进行任何上下文切换。
这意味着它将阻止。
这也意味着你的函数必须是 awaitable
,它必须是 yielding.
Or will it be locked when in use
如果可能存在竞争条件,它不会自动锁定。您需要锁定它 (参见 asyncio.Lock())。
但是如果你的代码没有做任何上下文切换。你不用担心,因为两个协程不能同时执行,并发不是并行.
在事件循环(协程执行的地方),协程产生一个事件,它想在该事件上恢复。事件循环能够等待这些事件的发生。但是在等待它的同时它也可以等待其他事件,或者它可以处理其他事件。这仅在您有协程时有效,事件通信由事件循环的屈服控制完成。
但是当两个事件共享同一个事件时你应该做什么List
,你需要锁定它。
为了更清楚地说明这一点,假设您的代码是一家餐厅,协程是女服务员,您向女服务员下订单,然后她前往厨师(事件循环 )
你需要等待,而且你不能分享酋长,而酋长可以同时做两个汉堡(处理两个不同的事件)
如果他没有足够大的网格来同时制作两个汉堡包(共享对象),会发生什么情况?
当您的订单正在等待时,您需要锁定网格两个让其他客户订购,但您不能共享网格,因为您有一个并且您需要说“嘿我需要锁定这里”
但你仍然可以吃沙拉(其他协程不会被阻塞)。
So in this case, would it be slower if I did Service().start() in each of them?
TL;DR
这个问题完全取决于您的 start()
函数的作用和作用。
好的,但我需要更好地理解 asyncio。
那么假设你有下面的代码
async def x():
a = await service.start()
return a
- 这将为
service().start()
的产生变量分配堆栈 space
- 事件循环将执行此语句并跳转到下一条语句
- 一旦
start()
get 被执行,它将压入调用堆栈的值
- 这将存储堆栈和指令指针。
- 然后它将产生的变量从
service().start()
存储到a
,然后它会恢复堆栈和指令指针。
- 当涉及到
return a
时,这会将a的值压入调用堆栈。
- 毕竟它会清除堆栈和指令指针。
请注意,我们之所以能够做到这一切,是因为 service().start()
是协程,它是 yielding
而不是返回。
乍一看你可能不太清楚,但正如我提到的,async
和 await
只是用于声明和管理协程的奇特语法。
import asyncio
@asyncio.coroutine
def decorated(x):
yield from x
async def native(x):
await x
但这两个函数完全相同,做的事情完全一样。您可以想到 yield from
将一个和多个函数链接在一起。
但是要深入理解异步 I/O,我们需要了解它的作用以及它在底层的作用。
在大多数操作系统中,基本的 API 可用于 select()
或 poll()
系统调用。
这些界面使 API 的用户能够检查是否有任何传入的 I/O 应该被处理。
例如,您的 HTTP 服务器想要检查是否有任何网络数据包到达以便为它们提供服务。借助此系统调用,您可以检查这一点。
当我们查看 select()
的 手册页 时,我们将看到此描述。
select() and pselect() allow a program to monitor multiple file de‐
scriptors, waiting until one or more of the file descriptors become
"ready" for some class of I/O operation (e.g., input possible). A file
descriptor is considered ready if it is possible to perform a corre‐
sponding I/O operation
这为您提供了一个非常基本的概念,并解释了异步 I/O 的本质。
它让您检查描述符是否可以读取和写入。
它使您的代码更 可扩展性 ,因为它不会阻塞其他东西。作为奖励,您的代码变得更快,但这不是异步 I/O 的实际目的。
所以要收拾一下。
事件循环一直在屈服,而有些东西已经准备好了。通过这样做它不会阻塞。
如果我有以下代码,变量 service
将如何影响端点的异步性质?变量会被共享吗?或者它会在使用时被锁定,从而阻止其他端点访问它,直到当前端点完成为止?
我假设服务实例是无状态的,即如果我在每个端点中创建一个服务实例,那将是等效的。我不太愿意这样做,因为我不知道哪个更耗时,实例化和销毁一个Service对象还是共享一个?
from typing import List, Union
from fastapi import APIRouter, Body, Depends
# Service definition
router = APIRouter()
service = Service()
@router.post("/a", response_model=Union[A, None])
async def x():
service.start()
pass
@router.post("/b", response_model=Union[B, None])
async def y():
service.stop()
pass
How would the variable service affect the asynchronous nature of the endpoints?
首先,如果您的 service.stop()
不是协程,asyncio 不会进行任何上下文切换。
这意味着它将阻止。
这也意味着你的函数必须是 awaitable
,它必须是 yielding.
Or will it be locked when in use
如果可能存在竞争条件,它不会自动锁定。您需要锁定它 (参见 asyncio.Lock())。
但是如果你的代码没有做任何上下文切换。你不用担心,因为两个协程不能同时执行,并发不是并行.
在事件循环(协程执行的地方),协程产生一个事件,它想在该事件上恢复。事件循环能够等待这些事件的发生。但是在等待它的同时它也可以等待其他事件,或者它可以处理其他事件。这仅在您有协程时有效,事件通信由事件循环的屈服控制完成。
但是当两个事件共享同一个事件时你应该做什么List
,你需要锁定它。
为了更清楚地说明这一点,假设您的代码是一家餐厅,协程是女服务员,您向女服务员下订单,然后她前往厨师(事件循环 )
你需要等待,而且你不能分享酋长,而酋长可以同时做两个汉堡(处理两个不同的事件)
如果他没有足够大的网格来同时制作两个汉堡包(共享对象),会发生什么情况?
当您的订单正在等待时,您需要锁定网格两个让其他客户订购,但您不能共享网格,因为您有一个并且您需要说“嘿我需要锁定这里”
但你仍然可以吃沙拉(其他协程不会被阻塞)。
So in this case, would it be slower if I did Service().start() in each of them?
TL;DR
这个问题完全取决于您的 start()
函数的作用和作用。
好的,但我需要更好地理解 asyncio。
那么假设你有下面的代码
async def x():
a = await service.start()
return a
- 这将为
service().start()
的产生变量分配堆栈 space
- 事件循环将执行此语句并跳转到下一条语句
- 一旦
start()
get 被执行,它将压入调用堆栈的值 - 这将存储堆栈和指令指针。
- 然后它将产生的变量从
service().start()
存储到a
,然后它会恢复堆栈和指令指针。
- 一旦
- 当涉及到
return a
时,这会将a的值压入调用堆栈。 - 毕竟它会清除堆栈和指令指针。
请注意,我们之所以能够做到这一切,是因为 service().start()
是协程,它是 yielding
而不是返回。
乍一看你可能不太清楚,但正如我提到的,async
和 await
只是用于声明和管理协程的奇特语法。
import asyncio
@asyncio.coroutine
def decorated(x):
yield from x
async def native(x):
await x
但这两个函数完全相同,做的事情完全一样。您可以想到 yield from
将一个和多个函数链接在一起。
但是要深入理解异步 I/O,我们需要了解它的作用以及它在底层的作用。
在大多数操作系统中,基本的 API 可用于 select()
或 poll()
系统调用。
这些界面使 API 的用户能够检查是否有任何传入的 I/O 应该被处理。
例如,您的 HTTP 服务器想要检查是否有任何网络数据包到达以便为它们提供服务。借助此系统调用,您可以检查这一点。
当我们查看 select()
的 手册页 时,我们将看到此描述。
select() and pselect() allow a program to monitor multiple file de‐ scriptors, waiting until one or more of the file descriptors become "ready" for some class of I/O operation (e.g., input possible). A file descriptor is considered ready if it is possible to perform a corre‐ sponding I/O operation
这为您提供了一个非常基本的概念,并解释了异步 I/O 的本质。
它让您检查描述符是否可以读取和写入。
它使您的代码更 可扩展性 ,因为它不会阻塞其他东西。作为奖励,您的代码变得更快,但这不是异步 I/O 的实际目的。
所以要收拾一下。
事件循环一直在屈服,而有些东西已经准备好了。通过这样做它不会阻塞。