问题
If I had the below code, how would the variable service
affect the asynchronous nature of the endpoints? Will the variable be shared? Or will it be locked when in use, thus blocking other endpoints from accessing it until the current one is done?
I ask the above assuming that Service instances are stateless, i.e. it would be equivalent if I created an instance of Service in each endpoint. I am reluctant to do that because I don't know which is more time consuming, instantiating and destroying a Service object or sharing one?
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
回答1:
How would the variable service affect the asynchronous nature of the endpoints?
First, if your service.stop()
is not a coroutine asyncio does not do any context switching.
This means it is going to block.
This also means your function must be awaitable
, it must be yielding.
Or will it be locked when in use
It does not lock automatically if there are race conditions possible. You need to lock it (see asyncio.Lock()).
But if your code does not do any context switching. You do not need to worry about that because two coroutines can not be executed at the same time, concurrency is not parallelism.
In event loops (where coroutines are executed), a coroutine yields an event on which it wants to resume. Event loop is capable of waiting for these events to happen. But while waiting for that it can also wait for other events, or it can process other events. This only works when you have a coroutine, the event communication is done by the event loop's yielding control.
But what you should do when two events are sharing the same List
, you need to Lock it.
To make this more clear, imagine your code is a restaurant and the coroutine is the waitress, you give orders to the waitress, then she heads to the chef (event loop)
You need to wait, also you can not share the chief, while the chief can make two hamburgers at the same time (processing two different events)
What happens when he doesn't have a grid big enough to make two hamburgers at the same time (shared objects)?
While your order is waiting you need to lock the grid two make other customers order, but you can not share the grid because you have one and you need to say "hey i need to lock here down"
But you can still have a salad (other coroutines doesn't get blocked).
So in this case, would it be slower if I did Service().start() in each of them?
TL;DR
The question completely depends on what your start()
function does and how it does.
Okay, but i need to understand asyncio better.
Then assume you have the following code
async def x():
a = await service.start()
return a
- This will allocate the stack space for the yielding variable of
service().start()
- The event loop will execute this and jump to the next statement
- once
start()
get's executed it will push the value of the calling stack - This will store the stack and the instruction pointer.
- Then it will store the yielded variable from
service().start()
toa
, then it will restore the stack and the instruction pointer.
- once
- When it comes to
return a
this will push the value of a to calling stack. - After all it will clear the stack and the instruction pointer.
Note that we were able to do all this because service().start()
is a coroutine, it is yielding
instead of returning.
This may not be clear to you at first glance but as I mentioned async
and await
are just fancy syntax for declaring and managing coroutines.
import asyncio
@asyncio.coroutine
def decorated(x):
yield from x
async def native(x):
await x
But these two function are identical does the exact same thing. You can think of yield from
chains one and more functions together.
But to understand asynchronous I/O deeply we need to have an understanding of what it does and how it does underneath.
In most operating systems, a basic API is available with select()
or poll()
system calls.
These interfaces enable the user of the API to check whether there is any incoming I/O that should be attended to.
For example, your HTTP server wants to check whether any network packets have arrived in order to service them. With the help of this system calls you are able to check this.
When we check the manual page of select()
we will see this description.
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
This gives you a pretty basic idea, and this explains the nature of what asynchronous I/O does.
It lets you check whether descriptors can be read and can be written.
It makes your code more scalable, by not blocking other things. Your code becomes faster as a bonus, but it is not the actual purpose of asynchronous I/O.
So to tidy up.
The event loop just keeps yielding, while something is ready. By doing that it does not block.
来源:https://stackoverflow.com/questions/65410635/python-fastapi-async-variable-sharing