Zijn globale variabelen thread-safe in Flask? Hoe deel ik gegevens tussen verzoeken?

In mijn toepassing wordt de status van een gemeenschappelijk object gewijzigd door verzoeken te doen, en het antwoord hangt af van de status.

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

Als ik dit op mijn ontwikkelserver uitvoer, verwacht ik 1, 2, 3 enzovoort te krijgen. Kan er iets misgaan als verzoeken van 100 verschillende klanten tegelijk worden gedaan? Het verwachte resultaat zou zijn dat de 100 verschillende klanten elk een uniek nummer van 1 tot 100 zien. Of gebeurt er zoiets:

  1. Klant 1-query’s. self.paramwordt met 1 verhoogd.
  2. Voordat de return-instructie kan worden uitgevoerd, schakelt de thread over naar client 2. self.paramwordt opnieuw verhoogd.
  3. De thread schakelt terug naar client 1, en de client krijgt bijvoorbeeld nummer 2 terug.
  4. Nu gaat de thread naar cliënt 2 en geeft hem/haar het cijfer 3.

Omdat er slechts twee klanten waren, waren de verwachte resultaten 1 en 2, niet 2 en 3. Er is een nummer overgeslagen.

Gebeurt dit ook als ik mijn applicatie opschaal? Naar welke alternatieven voor een globale variabele moet ik kijken?


Antwoord 1, autoriteit 100%

U kunt geen globale variabelen gebruiken om dit soort gegevens te bewaren. Het is niet alleen niet thread-safe, het is niet procesveilig, en WSGI-servers in productie brengen meerdere processen voort. Uw tellingen zouden niet alleen verkeerd zijn als u threads zou gebruiken om verzoeken af ​​te handelen, ze zouden ook variëren afhankelijk van het proces dat het verzoek heeft afgehandeld.

Gebruik een gegevensbron buiten Flask om globale gegevens te bewaren. Een database, memcached of redis zijn allemaal geschikte afzonderlijke opslaggebieden, afhankelijk van uw behoeften. Als u Python-gegevens moet laden en openen, overweeg dan multiprocessing.Manager. U kunt de sessie ook gebruiken voor eenvoudige gegevens per gebruiker.


De ontwikkelserver kan in één thread en proces draaien. U zult het gedrag dat u beschrijft niet zien, aangezien elk verzoek synchroon wordt afgehandeld. Schakel threads of processen in en u zult het zien. app.run(threaded=True)of app.run(processes=10). (In 1.0 is de server standaard voorzien van een thread.)


Sommige WSGI-servers ondersteunen mogelijk gevent of een andere asynchrone worker. Globale variabelen zijn nog steeds niet draadveilig omdat er nog steeds geen bescherming is tegen de meeste race-omstandigheden. Je kunt nog steeds een scenario hebben waarin de ene arbeider een waarde krijgt, opbrengst geeft, een andere deze aanpast, opbrengt, en de eerste arbeider past deze ook aan.


Als u tijdenseen aantal algemene gegevens moet opslaan, kunt u Flask’s g-object. Een ander veelvoorkomend geval is een object op het hoogste niveau dat databaseverbindingen beheert. Het onderscheid voor dit type “algemeen” is dat het uniek is voor elk verzoek, niet wordt gebruikt tussenverzoeken, en dat er iets is dat het opzetten en afbreken van de bron beheert.


Antwoord 2, autoriteit 37%

Dit is niet echt een antwoord op threadveiligheid van globals.

Maar ik denk dat het belangrijk is om hier sessies te vermelden.
U zoekt een manier om klantspecifieke gegevens op te slaan. Elke verbinding moet toegang hebben tot zijn eigen gegevenspool, op een threadveilige manier.

Dit is mogelijk met sessies aan de serverzijde en ze zijn beschikbaar in een zeer nette flask-plug-in: https:/ /pythonhosted.org/Flask-Session/

Als je sessies instelt, is een variabele sessionbeschikbaar in al je routes en deze gedraagt ​​zich als een woordenboek. De gegevens die in dit woordenboek zijn opgeslagen, zijn individueel voor elke verbindende klant.

Hier is een korte 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()

Nadat pip install Flask-Sessionheeft geïnstalleerd, zou je dit moeten kunnen uitvoeren. Probeer het vanuit verschillende browsers te openen, je zult zien dat de teller niet tussen hen wordt gedeeld.


Antwoord 3, autoriteit 7%

Een ander voorbeeld van een externe gegevensbron voor verzoeken is een cache, zoals die wordt geleverd door Flask-cachingof een andere extensie.

  1. Maak een bestand common.pyen plaats daarin het volgende:
from flask_caching import Cache
# Instantiate the cache
cache = Cache()
  1. In het bestand waarin uw flask appis gemaakt, registreert u uw cache met de volgende code:
# Import cache
from common import cache
# ...
app = Flask(__name__)
cache.init_app(app=app, config={"CACHE_TYPE": "filesystem",'CACHE_DIR': Path('/tmp')})
  1. Gebruik het nu in je hele applicatie door de cache te importeren en als volgt uit te voeren:
# Import cache
from common import cache
# store a value
cache.set("my_value", 1_000_000)
# Get a value
my_value = cache.get("my_value")

Antwoord 4, autoriteit 3%

Terwijl we de eerdere geüpdatete antwoorden volledig accepteren en het gebruik van globale variabelen voor productie en schaalbare Flask-opslag ontmoedigen, met het oog op prototyping of echt eenvoudige servers, die onder de flask ‘ontwikkelingsserver’ draaien…

De ingebouwde gegevenstypen van Python, en ik heb persoonlijk het globale dict, volgens Python-documentatiezijn threadveilig. Niet procesveilig.

Het invoegen, opzoeken en lezen van een dergelijk (algemeen server) dict zal in orde zijn van elke (mogelijk gelijktijdige) Flask-sessie die wordt uitgevoerd onder de ontwikkelingsserver.

Als zo’n globaal dict wordt gecodeerd met een unieke Flask-sessiesleutel, kan het behoorlijk handig zijn voor server-side opslag van sessiespecifieke gegevens die anders niet in de cookie passen (max. grootte 4 kB).

Natuurlijk moet zo’n wereldwijd serverdictaat zorgvuldig worden bewaakt omdat het te groot wordt, omdat het in het geheugen zit. Een soort van verlopen van de ‘oude’ sleutel/waarde-paren kan worden gecodeerd tijdens de verwerking van de aanvraag.

Nogmaals, het wordt niet aanbevolen voor productie- of schaalbare implementaties, maar het is mogelijk OK voor lokale taakgeoriënteerde servers waar een afzonderlijke database te veel is voor de gegeven taak.

Other episodes