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

101

En mi aplicación, el estado de un objeto común se cambia al realizar 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 las solicitudes se realizan desde 100 clientes diferentes simultáneamente, ¿puede salir mal algo? El resultado esperado sería que los 100 clientes diferentes vean cada uno un número único del 1 al 100. O ocurrirá algo como esto:

  1. Consultas del cliente 1. self.paramse incrementa en 1.
  2. Antes de que se pueda ejecutar la declaración de retorno, el subproceso 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, digamos.
  4. Ahora el hilo pasa al cliente 2 y le devuelve el número 3.

Dado que 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
fuente

Respuestas:

98

No puede usar variables globales para contener este tipo de datos. No solo no es seguro para subprocesos, no es seguro para procesos , y los servidores WSGI en producción generan múltiples procesos. Sus recuentos no solo serían incorrectos si estuviera usando 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 usar la sesión para datos simples por usuario.


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


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


Si necesita almacenar algunos datos globales durante una solicitud, puede usar el gobjeto de Flask . Otro caso común es algún objeto de nivel superior que administra 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 la eliminación del recurso.

davidismo
fuente
30

Esta no es realmente una respuesta a la seguridad de 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 grupo de datos, de forma segura para subprocesos.

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

Si configura sesiones, una sessionvariable está 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ía poder ejecutar esto. Intente acceder a él desde diferentes navegadores, verá que el contador no se comparte entre ellos.

lhk
fuente
3

Si bien acepta totalmente las respuestas anteriores y desalienta el uso de variables globales para la producción y el almacenamiento escalable de Flask, con el propósito de crear prototipos o servidores realmente simples, que se ejecutan bajo el 'servidor de desarrollo' de flask ...

...

Los tipos de datos integrados de Python, y yo personalmente usé y probé el global dict, según la documentación de Python, son seguros para subprocesos . No es seguro para el proceso .

Las inserciones, búsquedas y lecturas de dicho diccionario (global del servidor) serán correctas en cada sesión de Flask (posiblemente simultánea) que se ejecute en el servidor de desarrollo.

Cuando un dictado global de este tipo se codifica con una clave de sesión única de Flask, puede ser bastante útil para el almacenamiento en el lado del servidor de datos específicos de la sesión que, de lo contrario, no encajarían en la cookie (tamaño máximo 4 kB).

Por supuesto, un dictado global de servidor de este tipo debe protegerse cuidadosamente para que no crezca demasiado y esté en memoria. Se puede codificar algún tipo de expiración de los pares clave / valor 'antiguos' durante el procesamiento de la solicitud.

Nuevamente, no se recomienda para implementaciones escalables o de producción, pero posiblemente esté bien para servidores orientados a tareas locales donde una base de datos separada es demasiado para la tarea dada.

...

R. Simac
fuente