2015-08-30 8 views
5

Ich versuche, eine Kolbenerweiterung zu schreiben, die einige Informationen zwischen Anforderungen erhalten muss. Das funktioniert gut, wenn ich Werkzeug mit einem einzigen Prozess leite, aber wenn ich mit mehreren Prozessen laufe, bekomme ich seltsames Verhalten, das ich nicht verstehe. Nehmen Sie diese einfache Anwendung als Beispiel:Werkzeug und Klassenzustand mit Flask: Wie werden Klassengliedvariablen zurückgesetzt, wenn die Klasse nicht reinitialisiert wird?

from flask import Flask 
app = Flask(__name__) 

class Counter(object): 
    def __init__(self, app): 
     print('initializing a Counter object') 
     self.app = app 
     self.value = 0 

    def increment(self): 
     self.value += 1 
     print('Just incremented, current value is ', self.value) 

counter = Counter(app) 

@app.route('/') 
def index(): 
    for i in range(4): 
     counter.increment() 
    return 'index' 

if __name__ == '__main__': 
    #scenario 1 - single process 
    #app.run() 
    #scenario 2 - threaded 
    #app.run(threaded=True) 
    #scenario 3 - two processes 
    app.run(processes=2) 

Für die ersten beiden Szenarien verhält es sich genau so, wie ich erwarten würde: das Counter-Objekt wird einmal initialisiert und dann erhöht er mit jeder Anfrage an die ‚/‘ Route. Wenn ich es mit dem dritten Szenario laufen (vorbei Prozesse = 2), dann bekomme ich diese als Ausgabe:

initializing a Counter object 
    * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) 
Just incremented, current value is 1 
Just incremented, current value is 2 
Just incremented, current value is 3 
Just incremented, current value is 4 
127.0.0.1 - - [30/Aug/2015 09:47:25] "GET/HTTP/1.1" 200 - 
Just incremented, current value is 1 
Just incremented, current value is 2 
Just incremented, current value is 3 
Just incremented, current value is 4 
127.0.0.1 - - [30/Aug/2015 09:47:26] "GET/HTTP/1.1" 200 - 
Just incremented, current value is 1 
Just incremented, current value is 2 
Just incremented, current value is 3 
Just incremented, current value is 4 
127.0.0.1 - - [30/Aug/2015 09:47:27] "GET/HTTP/1.1" 200 - 

Es scheint, dass counter.value Rückkehr wird es nach Zustand ist richtig, ohne tatsächlich neu initialisiert wird initialisiert. Könnte jemand etwas Licht auf das werfen, was Werkzeug intern tut, um dies zu ermöglichen? Ich wäre auch sehr daran interessiert zu lernen, ob es einen Weg gibt, dieses Verhalten so zu gestalten, wie ich es naiv erwarten würde (zwei Prozesse, jeder mit einer eigenen Instanz von Counter). Vielen Dank!

+0

Haben Sie eine Lösung gefunden? Irgendwelche Implementierungsdetails? – user1658296

Antwort

4

Das erste Beispiel (single thread) verwendet nur einen Counter, so dass es funktioniert.

Das zweite Beispiel (mehrere Threads), Threads werden erzeugt, um jede Anfrage zu behandeln. Sie teilen den Speicher mit dem einen Counter, der erstellt wurde, bevor sie spawnen, also inkrementiert sie von jedem erhöht die gleiche Sache.

Das letzte Beispiel (mehrere Prozesse), Prozesse werden erzeugt, um jede Anfrage zu behandeln. Flask's dev server verwendet fork: jedes Kind sieht den gleichen Startpunkt (der Zähler ist bereits initialisiert), erhöht sich jedoch in own address space, das verschwindet, wenn die Anforderung endet.

import os 

class Counter: 
    def __init__(self): 
     print('init') 
     self.value = 0 

    def increment(self): 
     self.value += 1 
     print('inc -> {}'.format(self.value)) 

counter = Counter() 

def multi(): 
    if not os.fork(): 
     # child starts with copy of parent memory 
     for _ in range(3): 
      # increments three times 
      counter.increment() 

     # child is done 
     os._exit(0) 

# three processes run 
for _ in range(3): 
    multi() 
init 
inc -> 1 
inc -> 2 
inc -> 3 
inc -> 1 
inc -> 2 
inc -> 3 
inc -> 1 
inc -> 2 
inc -> 3 

einer Datenbank oder einem anderen externen Speicher Verwenden Sie über Prozesse globalen Zustand zu speichern, mit before_ und after_request. Beachten Sie, dass es nicht gerade einfach ist, da Sie den erhöhten Wert des Zählers von jeder Thread-sicheren Anforderung speichern müssen, damit zwei Threads den Wert nicht gleichzeitig überschreiben.

req 1 starts, gets stored value = 4 
req 2 starts, gets stored value = 4 
req 1 increments, value = 8 
req 1 saves, value = 8 
req 2 increments, value = 8 
req 2 saves, value = 8 but should = 12 
+0

@davidism: Danke, das erklärt das Verhalten, das ich nicht verstand. Es scheint so, als würde der gesamte Speicher aus der Anwendung für jede einzelne Anforderung langsamer erstellt als nur kontinuierlich laufende Prozesse zu verwalten. Weißt du, warum es stattdessen Forking getan hat? – Ivanna

+0

@Ivanna Ich habe keinen Einblick in die Köpfe der Entwickler, nicht ob dies eine schlechte Leistung ist, aber ich würde es vermeiden, eine solche Annahme zu machen. So funktioniert der Dev-Server, und es ist einfach zu implementieren. Sie sollten sowieso einen echten Server in der Produktion verwenden, z. B. uwsgi, der das möglicherweise anders macht. – davidism

Verwandte Themen