He estado jugando con Flask y FastAPI para ver cómo actúa como servidor.
Una de las cosas principales que me gustaría saber es cómo Flask y FastAPI manejan múltiples solicitudes de múltiples clientes.
Especialmente cuando el código tiene problemas de eficiencia (largo tiempo de consulta de la base de datos).
Entonces, traté de hacer un código simple para entender este problema.
El código es simple, cuando el cliente accede a la ruta, la aplicación duerme durante 10 segundos antes de devolver los resultados.
Se ve algo como esto:
API rápida
import uvicorn from fastapi import FastAPI from time import sleep app = FastAPI() @app.get('/') async def root(): print('Sleeping for 10') sleep(10) print('Awake') return {'message': 'hello'} if __name__ == "__main__": uvicorn.run(app, host="127.0.0.1", port=8000)Matraz
from flask import Flask from flask_restful import Resource, Api from time import sleep app = Flask(__name__) api = Api(app) class Root(Resource): def get(self): print('Sleeping for 10') sleep(10) print('Awake') return {'message': 'hello'} api.add_resource(Root, '/') if __name__ == "__main__": app.run()Una vez que las aplicaciones están activas, intenté acceder a ellas al mismo tiempo a través de 2 clientes de Chrome diferentes. Los siguientes son los resultados:
API rápida

Matraz

Como puede ver, para FastAPI, el código primero espera 10 segundos antes de procesar la siguiente solicitud. Mientras que para Flask, el código procesa la siguiente solicitud mientras aún se está produciendo la suspensión de 10 segundos.
A pesar de buscar un poco en Google, no hay realmente una respuesta directa sobre este tema.
Si alguien tiene algún comentario que pueda arrojar algo de luz sobre esto, por favor déjelo en los comentarios.
Sus opiniones son todas apreciadas. Muchas gracias a todos por su tiempo.
EDITAR Una actualización sobre esto, estoy explorando un poco más y encontré este concepto de administrador de procesos. Por ejemplo, podemos ejecutar uvicorn usando un administrador de procesos (gunicorn). Al agregar más trabajadores, puedo lograr algo como Flask. Todavía probando los límites de esto, sin embargo. https://www.uvicorn.org/despliegue/
¡Gracias a todos los que dejaron comentarios! Lo aprecio.
Creo que está bloqueando una cola de eventos en FastAPI, que es un marco asíncrono, mientras que en Flask las solicitudes probablemente se ejecutan cada una en un nuevo hilo. Mueva todas las tareas vinculadas a la CPU a procesos separados o, en su ejemplo de FastAPI, simplemente duerma en el bucle de eventos (no use time.sleep aquí). En FastAPI, ejecute tareas vinculadas a IO de forma asíncrona
Está utilizando la función time.sleep() , en un punto final async . time.sleep() está bloqueando y nunca debe usarse en código asíncrono. Lo que debería usar es probablemente la función asyncio.sleep() :
import asyncio import uvicorn from fastapi import FastAPI app = FastAPI() @app.get('/') async def root(): print('Sleeping for 10') await asyncio.sleep(10) print('Awake') return {'message': 'hello'} if __name__ == "__main__": uvicorn.run(app, host="127.0.0.1", port=8000)De esa manera, cada solicitud tardará unos 10 segundos en completarse, pero podrá atender varias solicitudes al mismo tiempo.
En general, los marcos asíncronos ofrecen reemplazos para todas las funciones de bloqueo dentro de la biblioteca estándar (funciones de suspensión, funciones de IO, etc.). Debe usar esos reemplazos al escribir código asíncrono y (opcionalmente) await .
Algunos marcos y bibliotecas que no bloquean, como gevent, no ofrecen reemplazos. En su lugar, utilizan funciones de parche de mono en la biblioteca estándar para que no bloqueen. Sin embargo, este no es el caso, hasta donde yo sé, para los marcos y bibliotecas asíncronos más nuevos, porque están destinados a permitir que el desarrollador use la sintaxis asíncrona-espera.
Las operaciones de bloqueo detendrán el bucle de eventos que ejecuta las tareas. Cuando llama a la función sleep() , todas las tareas (solicitudes) esperan hasta que finaliza, lo que elimina todos los beneficios de la ejecución de código asíncrono.
Para comprender por qué este código es incorrecto para la comparación, debemos comprender mejor cómo funciona el código asíncrono en Python y tener algún conocimiento de GIL. La simultaneidad y el código asíncrono están bien explicados en los documentos de FastAPI.
@Asotos ha descrito por qué su código es lento y sí, debe usar rutinas para las operaciones de E/S, ya que bloquean la ejecución del bucle de eventos ( sleep() es una operación de bloqueo). Se sugiere razonablemente usar funciones asíncronas para que el bucle de eventos no se bloquee, pero por ahora, no todas las bibliotecas tienen versiones asíncronas.
async y asyncio.sleep En caso de que no pueda usar la versión asíncrona de la biblioteca, simplemente puede definir sus funciones de ruta como funciones de def simples, no como async def .
Si la función de ruta se define como síncrona ( def ), FastAPI llamará inteligentemente a esta función en un grupo de subprocesos externos, y el subproceso principal con bucle de eventos no se bloqueará, y sus puntos de referencia serán mucho mejores sin usar await asyncio.sleep() . Muy bien explicado en esta sección.
from time import sleep import uvicorn from fastapi import FastAPI app = FastAPI() @app.get('/') def root(): print('Sleeping for 10') sleep(10) print('Awake') return {'message': 'hello'} if __name__ == "__main__": uvicorn.run(app, host="127.0.0.1", port=8000)Por cierto, no obtendrá muchos beneficios si las operaciones que se ejecutan en el grupo de subprocesos están vinculadas a la CPU (por ejemplo, cálculos pesados) debido a GIL . Las tareas vinculadas a la CPU deben ejecutarse en procesos separados.