¿Son seguras las hebras globales en el matraz? ¿Cómo comparto datos entre solicitudes?

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 se realizan solicitudes de 100 clientes diferentes simultáneamente, ¿puede salir algo mal? El resultado esperado sería que los 100 clientes diferentes vean un número único de 1 a 100. O sucederá algo como esto:

  1. Consultas del cliente 1. self.param se incrementa en 1.
  2. Antes de que se pueda ejecutar la statement de retorno, el hilo cambia al cliente 2. self.param se incrementa nuevamente.
  3. El hilo vuelve al cliente 1, y el cliente recibe el número 2, por ejemplo.
  4. Ahora el hilo se mueve 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.

¿Esto sucederá realmente a medida que amplío mi aplicación? ¿Qué alternativas a una variable global debo mirar?

No puede utilizar variables globales para mantener 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 cuentas no solo serían incorrectas si estuviera usando subprocesos para manejar solicitudes, sino que también variarían dependiendo de qué proceso 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 la posibilidad de multiprocessing.Manager . También puede utilizar 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á de forma sincrónica. Habilita hilos o procesos y lo verás. app.run(threaded=True) o app.run(processes=10) . (En la versión 1.0, el servidor está enlazado por defecto.)


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, los rendimientos, otro lo modifica, los rendimientos, y luego el primer trabajador también lo modifica.


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

Esto no es realmente una respuesta a la seguridad de hilos de los globales.

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

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

Si configura sesiones, una variable de session está disponible en todas sus rutas y se comporta como un diccionario. Los datos almacenados en este diccionario son individuales para cada cliente de conexión.

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 "counters 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 de que pip install Flask-Session , debería poder ejecutar esto. Intente acceder desde diferentes navegadores, verá que el contador no se comparte entre ellos.