Sind globale Variablen im Kolben threadsicher? Wie teile ich Daten zwischen Anfragen?

96

In meiner App wird der Status eines gemeinsamen Objekts durch Anfragen geändert, und die Antwort hängt vom Status ab.

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')

Wenn ich dies auf meinem Entwicklungsserver ausführe, erwarte ich 1, 2, 3 und so weiter. Kann etwas schief gehen, wenn Anfragen von 100 verschiedenen Clients gleichzeitig gestellt werden? Das erwartete Ergebnis wäre, dass die 100 verschiedenen Clients jeweils eine eindeutige Zahl von 1 bis 100 sehen. Oder wird so etwas passieren:

  1. Client 1-Abfragen. self.paramwird um 1 erhöht.
  2. Bevor die return-Anweisung ausgeführt werden kann, wechselt der Thread zu Client 2. self.paramwird erneut inkrementiert.
  3. Der Thread wechselt zurück zu Client 1, und dem Client wird beispielsweise die Nummer 2 zurückgegeben.
  4. Jetzt bewegt sich der Thread zu Client 2 und gibt ihm die Nummer 3 zurück.

Da es nur zwei Kunden gab, waren die erwarteten Ergebnisse 1 und 2, nicht 2 und 3. Eine Nummer wurde übersprungen.

Wird dies tatsächlich passieren, wenn ich meine Bewerbung vergrößere? Welche Alternativen zu einer globalen Variablen sollte ich prüfen?

sayantankhan
quelle

Antworten:

91

Sie können keine globalen Variablen verwenden, um diese Art von Daten zu speichern. Es ist nicht nur nicht threadsicher, sondern auch nicht prozesssicher , und WSGI-Server in der Produktion erzeugen mehrere Prozesse. Ihre Zählungen wären nicht nur falsch, wenn Sie Threads zur Bearbeitung von Anforderungen verwenden würden, sondern sie würden auch variieren, je nachdem, welcher Prozess die Anforderung verarbeitet hat.

Verwenden Sie eine Datenquelle außerhalb von Flask, um globale Daten zu speichern. Eine Datenbank, ein Memcached oder ein Redis sind je nach Ihren Anforderungen geeignete separate Speicherbereiche. Wenn Sie Python-Daten laden und darauf zugreifen müssen, ziehen Sie dies in Betracht multiprocessing.Manager. Sie können die Sitzung auch für einfache Daten pro Benutzer verwenden.


Der Entwicklungsserver kann in einem einzelnen Thread und Prozess ausgeführt werden. Das von Ihnen beschriebene Verhalten wird nicht angezeigt, da jede Anforderung synchron behandelt wird. Aktivieren Sie Threads oder Prozesse und Sie werden es sehen. app.run(threaded=True)oder app.run(processes=10). (In 1.0 ist der Server standardmäßig mit einem Thread versehen.)


Einige WSGI-Server unterstützen möglicherweise gevent oder einen anderen asynchronen Worker. Globale Variablen sind immer noch nicht threadsicher, da es immer noch keinen Schutz gegen die meisten Rennbedingungen gibt. Sie können immer noch ein Szenario haben, in dem ein Mitarbeiter einen Wert erhält, ergibt, ein anderer ihn ändert, ergibt, dann ändert der erste Mitarbeiter ihn ebenfalls.


Wenn Sie während einer Anforderung einige globale Daten speichern müssen , können Sie das gObjekt von Flask verwenden . Ein weiterer häufiger Fall ist ein Objekt der obersten Ebene, das Datenbankverbindungen verwaltet. Der Unterschied für diese Art von "global" besteht darin, dass sie für jede Anforderung eindeutig ist und nicht zwischen Anforderungen verwendet wird, und dass das Einrichten und Herunterfahren der Ressource verwaltet wird.

Davidismus
quelle
24

Dies ist keine wirkliche Antwort auf die Thread-Sicherheit von Globals.

Aber ich denke, es ist wichtig, hier Sitzungen zu erwähnen. Sie suchen nach einer Möglichkeit, kundenspezifische Daten zu speichern. Jede Verbindung sollte threadsicher auf ihren eigenen Datenpool zugreifen können.

Dies ist bei serverseitigen Sitzungen möglich und sie sind in einem sehr übersichtlichen Flask-Plugin verfügbar: https://pythonhosted.org/Flask-Session/

Wenn Sie Sitzungen einrichten, sessionist auf allen Routen eine Variable verfügbar, die sich wie ein Wörterbuch verhält. Die in diesem Wörterbuch gespeicherten Daten sind für jeden Verbindungsclient individuell.

Hier ist eine kurze Demo:

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()

Danach pip install Flask-Sessionsollten Sie dies ausführen können. Wenn Sie versuchen, über verschiedene Browser darauf zuzugreifen, werden Sie feststellen, dass der Zähler nicht zwischen ihnen geteilt wird.

lhk
quelle
2

Während die oben genannten Antworten vollständig akzeptiert werden und die Verwendung von Globals für die Produktion und die skalierbare Speicherung von Kolben zum Zweck des Prototyping oder von wirklich einfachen Servern, die unter dem Kolben-Entwicklungsserver ausgeführt werden, nicht empfohlen wird ...

... die in Python integrierten Datentypen, und ich persönlich habe die globalen dictgemäß Python-Dokumenten ( https://docs.python.org/3/glossary.html#term-global-interpreter-lock ) verwendet und getestet fadensicher . Nicht prozesssicher .

Die Einfügungen, Suchvorgänge und Lesevorgänge von einem solchen (Server global) Diktat sind für jede (möglicherweise gleichzeitige) Flask-Sitzung, die unter dem Entwicklungsserver ausgeführt wird, in Ordnung.

Wenn ein solches globales Diktat mit einem eindeutigen Flask-Sitzungsschlüssel verschlüsselt ist, kann es für die serverseitige Speicherung sitzungsspezifischer Daten sehr nützlich sein, da es sonst nicht in das Cookie passt (maximale Größe 4 KB).

Natürlich sollte ein solches globales Server-Diktat sorgfältig geschützt werden, damit es nicht zu groß wird und sich im Speicher befindet. Während der Anforderungsverarbeitung kann eine Art Ablauf der 'alten' Schlüssel / Wert-Paare codiert werden.

Auch dies wird nicht für Produktions- oder skalierbare Bereitstellungen empfohlen, ist jedoch möglicherweise für lokale aufgabenorientierte Server in Ordnung, bei denen eine separate Datenbank für die jeweilige Aufgabe zu viel ist

...

R. Simac
quelle