¿Las variables globales son seguras para subprocesos en Flask? ¿Cómo comparto datos entre solicitudes?

Resuelto sayantankhan asked hace 9 años • 4 respuestas

En mi aplicación, el estado de un objeto común se cambia mediante solicitudes y la respuesta depende del estado.

class SomeObj():
    def __init__(self, param):
        self.param = param
    def query(self):
        self.param += 1
        return self.param

global_obj = SomeObj(0)

@app.route('/')
def home():
    flash(global_obj.query())
    render_template('index.html')

Si ejecuto esto en mi servidor de desarrollo, espero obtener 1, 2, 3 y así sucesivamente. Si se realizan solicitudes de 100 clientes diferentes simultáneamente, ¿algo puede salir mal? El resultado esperado sería que los 100 clientes diferentes vean cada uno un número único del 1 al 100. ¿O sucederá algo como esto?

  1. Consultas del cliente 1. self.paramse incrementa en 1.
  2. Antes de que se pueda ejecutar la declaración de devolución, el hilo cambia al cliente 2. self.paramse incrementa nuevamente.
  3. El hilo vuelve al cliente 1 y al cliente se le devuelve el número 2, por ejemplo.
  4. Ahora el hilo pasa al cliente 2 y le devuelve el número 3.

Como solo había dos clientes, los resultados esperados fueron 1 y 2, no 2 y 3. Se omitió un número.

¿Sucederá esto realmente a medida que amplíe mi aplicación? ¿Qué alternativas a una variable global debería considerar?

sayantankhan avatar Sep 28 '15 10:09 sayantankhan
Aceptado

No puede utilizar variables globales para contener este tipo de datos. No sólo no es seguro para subprocesos, tampoco es seguro para procesos , y los servidores WSGI en producción generan múltiples procesos. Sus recuentos no solo serían incorrectos si estuviera utilizando subprocesos para manejar solicitudes, sino que también variarían según el proceso que manejó la solicitud.

Utilice una fuente de datos fuera de Flask para almacenar datos globales. Una base de datos, Memcached o Redis son áreas de almacenamiento separadas apropiadas, según sus necesidades. Si necesita cargar y acceder a datos de Python, considere multiprocessing.Manager. También puede utilizar la sesión para datos simples por usuario.


El servidor de desarrollo puede ejecutarse en un solo subproceso y proceso. No verá el comportamiento que describe ya que cada solicitud se manejará de forma sincrónica. Habilite hilos o procesos y lo verá. app.run(threaded=True)o app.run(processes=10). (En 1.0, el servidor tiene subprocesos de forma predeterminada).


Algunos servidores WSGI pueden admitir gevent u otro trabajador asíncrono. Las variables globales todavía no son seguras para subprocesos porque todavía no hay protección contra la mayoría de las condiciones de carrera. Todavía se puede tener un escenario en el que un trabajador obtiene un valor, lo cede, otro lo modifica, cede y luego el primer trabajador también lo modifica.


Si necesita almacenar algunos datos globales durante una solicitud, puede utilizar gel objeto de Flask . Otro caso común es algún objeto de nivel superior que gestiona las conexiones de la base de datos. La distinción para este tipo de "global" es que es único para cada solicitud, no se usa entre solicitudes y hay algo que administra la configuración y el desmantelamiento del recurso.

davidism avatar Sep 28 '2015 14:09 davidism

Esta no es realmente una respuesta a la seguridad de los subprocesos de los globales.

Pero creo que es importante mencionar las sesiones aquí. Está buscando una forma de almacenar datos específicos del cliente. Cada conexión debe tener acceso a su propio conjunto de datos, de forma segura para subprocesos.

Esto es posible con sesiones del lado del servidor y están disponibles en un complemento de flask muy elegante: https://pythonhosted.org/Flask-Session/

Si configura sesiones, sessionhay una variable disponible en todas sus rutas y se comporta como un diccionario. Los datos almacenados en este diccionario son individuales para cada cliente que se conecta.

Aquí hay una breve demostración:

from flask import Flask, session
from flask_session import Session

app = Flask(__name__)
# Check Configuration section for more details
SESSION_TYPE = 'filesystem'
app.config.from_object(__name__)
Session(app)

@app.route('/')
def reset():
    session["counter"]=0

    return "counter was reset"

@app.route('/inc')
def routeA():
    if not "counter" in session:
        session["counter"]=0

    session["counter"]+=1

    return "counter is {}".format(session["counter"])

@app.route('/dec')
def routeB():
    if not "counter" in session:
        session["counter"] = 0

    session["counter"] -= 1

    return "counter is {}".format(session["counter"])


if __name__ == '__main__':
    app.run()

Después pip install Flask-Session, deberías poder ejecutar esto. Prueba a acceder desde diferentes navegadores, verás que el contador no se comparte entre ellos.

lhk avatar Nov 05 '2018 10:11 lhk

Otro ejemplo de una fuente de datos externa a las solicitudes es un caché, como el que proporciona Flask-Caching u otra extensión.

  1. Crea un archivo common.pyy coloca en él lo siguiente:
from flask_caching import Cache

# Instantiate the cache
cache = Cache()
  1. En el archivo donde se flask appcrea, registre su caché con el siguiente código:
# Import cache
from common import cache

# ...
app = Flask(__name__)

cache.init_app(app=app, config={"CACHE_TYPE": "filesystem",'CACHE_DIR': Path('/tmp')})
  1. Ahora utilícelo en toda su aplicación importando el caché y ejecutando de la siguiente manera:
# Import cache
from common import cache

# store a value
cache.set("my_value", 1_000_000)

# Get a value
my_value = cache.get("my_value")
Yaakov Bressler avatar Nov 17 '2020 18:11 Yaakov Bressler