web-dev-qa-db-fra.com

Les variables globales sont-elles sécurisées dans le flacon? Comment partager des données entre des demandes?

Dans mon application, l'état d'un objet commun est modifié en faisant des demandes, et la réponse dépend de l'état.

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 je lance ceci sur mon serveur de développement, je m'attends à obtenir 1, 2, 3 et ainsi de suite. Si des demandes sont faites simultanément auprès de 100 clients différents, quelque chose peut-il mal se passer? Le résultat attendu serait que les 100 clients différents voient chacun un numéro unique de 1 à 100. Ou quelque chose comme ça se produira:

  1. Requêtes du client 1. self.param est incrémenté de 1.
  2. Avant que l'instruction de retour puisse être exécutée, le thread bascule sur le client 2. self.param est à nouveau incrémenté.
  3. Le thread revient au client 1 et le client est renvoyé le numéro 2, par exemple.
  4. Maintenant, le thread se déplace vers le client 2 et lui renvoie le numéro 3.

Puisqu'il n'y avait que deux clients, les résultats attendus étaient 1 et 2, et non 2 et 3. Un nombre a été ignoré.

Cela se produira-t-il réellement lorsque j'élargirai mon application? Quelles alternatives à une variable globale dois-je envisager?

59
sayantankhan

Vous ne pouvez pas utiliser de variables globales pour contenir ce type de données. Non seulement il n'est pas sûr pour les threads, il n'est pas sûr pour les processus , et les serveurs WSGI en production génèrent plusieurs processus. Non seulement vos chiffres seraient faux si vous utilisiez des threads pour gérer les demandes, mais ils varieraient également en fonction du processus qui a traité la demande.

Utilisez une source de données en dehors de Flask pour conserver les données globales. Une base de données, memcached ou redis sont toutes des zones de stockage distinctes appropriées, selon vos besoins. Si vous devez charger et accéder à Python data, considérez multiprocessing.Manager . Vous pouvez également utiliser la session pour des données simples par utilisateur.


Le serveur de développement peut s'exécuter en un seul thread et processus. Vous ne verrez pas le comportement que vous décrivez, car chaque demande sera traitée de manière synchrone. Activez les threads ou les processus et vous le verrez. app.run(threaded=True) ou app.run(processes=10). (Dans 1.0, le serveur est enfilé par défaut.)


Certains serveurs WSGI peuvent prendre en charge gevent ou un autre travailleur asynchrone. Les variables globales ne sont toujours pas thread-safe car il n'y a toujours pas de protection contre la plupart des conditions de concurrence. Vous pouvez toujours avoir un scénario dans lequel un travailleur obtient une valeur, cède, un autre la modifie, cède, puis le premier travailleur la modifie également.


Si vous avez besoin de stocker des données globales pendant une demande, vous pouvez utiliser l'objet g de Flask . Un autre cas courant est un objet de niveau supérieur qui gère les connexions à la base de données. La distinction pour ce type de "global" est qu'il est unique à chaque demande, non utilisé entre demandes, et qu'il y a quelque chose qui gère la configuration et le démontage de la ressource.

56
davidism

Ce n'est pas vraiment une réponse à la sécurité des threads des mondiaux.

Mais je pense qu'il est important de mentionner les sessions ici. Vous cherchez un moyen de stocker des données spécifiques au client. Chaque connexion doit avoir accès à son propre pool de données, d'une manière threadsafe.

Cela est possible avec les sessions côté serveur, et elles sont disponibles dans un plugin très soigné flask: https://pythonhosted.org/Flask-Session/

Si vous configurez des sessions, une variable session est disponible dans tous vos itinéraires et se comporte comme un dictionnaire. Les données stockées dans ce dictionnaire sont individuelles pour chaque client se connectant.

Voici une courte démo:

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 __== '__main__':
    app.run()

Après pip install Flask-Session, vous devriez pouvoir l'exécuter. Essayez d'y accéder à partir de différents navigateurs, vous verrez que le compteur n'est pas partagé entre eux.

8
lhk