Global değişkenler Flask'ta iş parçacığı için güvenli mi? Verileri istekler arasında nasıl paylaşırım?


101

Başvurumda ortak bir nesnenin durumu istekte bulunarak değiştiriliyor ve yanıt duruma göre değişiyor.

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

Bunu geliştirme sunucumda çalıştırırsam, 1, 2, 3 ve benzerlerini almayı bekliyorum. Aynı anda 100 farklı müşteriden istekte bulunulursa bir şeyler ters gidebilir mi? Beklenen sonuç, 100 farklı müşterinin her birinin 1'den 100'e kadar benzersiz bir sayı görmesi olacaktır. Veya bunun gibi bir şey olacak:

  1. İstemci 1 sorguları. self.param1 artırılır.
  2. Return ifadesi çalıştırılmadan önce, iş parçacığı istemciye 2. geçer self.param.
  3. İş parçacığı istemci 1'e geri döner ve istemciye örneğin 2 sayısı döndürülür.
  4. Şimdi iş parçacığı istemci 2'ye taşınır ve ona 3 sayısını verir.

Yalnızca iki müşteri olduğu için, beklenen sonuçlar 2 ve 3 değil 1 ve 2 idi. Bir sayı atlandı.

Başvurumu büyüttüğümde bu gerçekten olacak mı? Küresel bir değişkene hangi alternatiflere bakmalıyım?

Yanıtlar:


101

Bu tür verileri tutmak için global değişkenler kullanamazsınız. Yalnızca iş parçacığı için güvenli değil, işlem açısından güvenli değil ve üretimdeki WSGI sunucuları birden çok işlem doğuruyor. İstekleri işlemek için ileti dizileri kullanıyorsanız, sayılarınız yanlış olmaz, aynı zamanda isteği hangi işlemin ele aldığına bağlı olarak da değişir.

Global verileri tutmak için Flask dışında bir veri kaynağı kullanın. Bir veritabanı, memcached veya redis, ihtiyaçlarınıza bağlı olarak uygun ayrı depolama alanlarıdır. Python verilerini yüklemeniz ve bunlara erişmeniz gerekiyorsa düşünün multiprocessing.Manager. Oturumu, kullanıcı başına basit veriler için de kullanabilirsiniz.


Geliştirme sunucusu, tek iş parçacığı ve süreçte çalışabilir. Her istek eşzamanlı olarak işleneceğinden, tanımladığınız davranışı görmezsiniz. Konuları veya işlemleri etkinleştirin ve göreceksiniz. app.run(threaded=True)veya app.run(processes=10). (1.0'da sunucu varsayılan olarak iş parçacığına sahiptir.)


Bazı WSGI sunucuları, gevent veya başka bir eşzamansız çalışanı destekleyebilir. Küresel değişkenler hala iş parçacığı açısından güvenli değil çünkü çoğu yarış koşuluna karşı hala koruma yok. Hala bir işçinin bir değer aldığı, verdiği, diğerinin değiştirdiği, verdiği, sonra ilk çalışanın da onu değiştirdiği bir senaryoya sahip olabilirsiniz.


Bir istek sırasında bazı genel verileri depolamanız gerekirse, Flask'ın gnesnesini kullanabilirsiniz . Diğer bir yaygın durum, veritabanı bağlantılarını yöneten bazı üst düzey nesnelerdir. Bu tür "global" in farkı , her bir istek için benzersiz olması, istekler arasında kullanılmaması ve kaynağın kurulumunu ve sökülmesini yöneten bir şey olmasıdır.


31

Bu gerçekten küresellerin iş parçacığı güvenliğine bir cevap değil.

Ancak burada seanslardan bahsetmenin önemli olduğunu düşünüyorum. Müşteriye özgü verileri depolamanın bir yolunu arıyorsunuz. Her bağlantının kendi veri havuzuna iş parçacığı açısından güvenli bir şekilde erişimi olmalıdır.

Bu, sunucu taraflı oturumlarla mümkündür ve çok temiz bir şişe eklentisinde mevcuttur: https://pythonhosted.org/Flask-Session/

Seanslar ayarlarsanız session, tüm rotalarınızda bir değişken bulunur ve bir sözlük gibi davranır. Bu sözlükte saklanan veriler, bağlanan her istemci için ayrıdır.

İşte kısa bir 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()

Daha sonra pip install Flask-Session, bunu çalıştırabilmelisin. Farklı tarayıcılardan erişmeyi deneyin, sayacın aralarında paylaşılmadığını göreceksiniz.


3

Önceki yükseltilmiş yanıtları tamamen kabul ederken ve üretim ve ölçeklenebilir Flask depolaması için küresel değişkenlerin kullanılmasının cesaretini kırarken, prototip oluşturma veya gerçekten basit sunucular, şişenin 'geliştirme sunucusu' altında çalıştırılması için ...

...

Python yerleşik veri türleri ve ben kişisel olarak global kullandım ve test ettim dict, Python belgelerine göre iş parçacığı güvenli. İşlem güvenli değil .

Geliştirme sunucusu altında çalışan her (muhtemelen eşzamanlı) Flask oturumunda, böyle bir (sunucu genelindeki) dikteden yapılan eklemeler, aramalar ve okumalar uygun olacaktır.

Böyle bir genel kural, benzersiz bir Flask oturum anahtarıyla anahtarlandığında, bu, oturuma özgü verilerin sunucu tarafında depolanması için oldukça yararlı olabilir, aksi takdirde tanımlama bilgisine uymaz (maksimum boyut 4 kB).

Tabii ki, böyle bir sunucu küresel diktesi bellek içinde olmak, çok büyümek için dikkatlice korunmalıdır. 'Eski' anahtar / değer çiftlerinin süresi dolan bir tür istek işleme sırasında kodlanabilir.

Yine, üretim veya ölçeklenebilir dağıtımlar için tavsiye edilmez, ancak verilen görev için ayrı bir veritabanının çok fazla olduğu yerel görev odaklı sunucular için muhtemelen uygundur.

...


0

Uygulamanız için bir önbellek kullanmak bir geçici çözüm olabilir. Bir önbellek oluşturmak ve kullanmak şu şekilde yapılır:

  1. Bir dosya oluşturun common.pyve aşağıdakileri içine yerleştirin:
from flask_caching import Cache

# Instantiate the cache
cache = Cache()
  1. flask appOluşturduğunuz dosyada , önbelleğinizi aşağıdaki kodla kaydedin:
# Import cache
from common import cache

# ...
app = Flask(__name__)

cache.init_app(app=app, config={"CACHE_TYPE": "filesystem",'CACHE_DIR': Path('/tmp')})
  1. Şimdi önbelleği içe aktararak ve aşağıdaki gibi çalıştırarak uygulamanızın tamamında kullanın:
# 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")
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.