python - ¿Son seguras las hebras de las variables globales en el matraz? ¿Cómo comparto datos entre solicitudes?
flask thread-safety (2)
Esto no es realmente una respuesta a la seguridad de los 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 grupo de datos, de manera segura.
Esto es posible con 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 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 de
pip install Flask-Session
, debería poder ejecutar esto.
Intente acceder desde diferentes navegadores, verá que el contador no se comparte entre ellos.
En mi aplicación, el estado de un objeto común cambia al hacer 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:
-
Cliente 1 consultas.
self.param
se incrementa en 1. -
Antes de que se pueda ejecutar la instrucción return, el hilo cambia al cliente 2.
self.param
se incrementa nuevamente. - El subproceso vuelve al cliente 1 y el cliente recibe el número 2, por ejemplo.
- 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 realmente sucederá cuando escale mi aplicación? ¿Qué alternativas a una variable global debería mirar?
No puede usar variables globales para contener este tipo de datos. No solo no es seguro para subprocesos, no es seguro para los procesos , y los servidores WSGI en producción generan múltiples procesos. Sus recuentos no solo serían incorrectos si estuviera utilizando hilos para manejar solicitudes, sino que también variarían según el proceso que manejara la solicitud.
Use una fuente de datos fuera de Flask para contener 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 el
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 gestionará 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 está enhebrado por defecto).
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 puede tener un escenario en el que un trabajador obtiene un valor, rinde, otro lo modifica, rinde, 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 gestiona conexiones de bases de datos.
La distinción para este tipo de "global" es que es única para cada solicitud, no se utiliza
entre
solicitudes, y hay algo que gestiona la configuración y desmontaje del recurso.