web-dev-qa-db-ja.com

グローバル変数はフラスコでスレッドセーフですか?リクエスト間でデータを共有するにはどうすればよいですか?

私のアプリでは、リクエストを行うことで一般的なオブジェクトの状態が変更され、応答は状態に依存します。

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

開発サーバーでこれを実行すると、1、2、3などが取得されると予想されます。 100の異なるクライアントから同時にリクエストが行われた場合、何か問題が発生する可能性はありますか?予想される結果は、100の異なるクライアントがそれぞれ1〜100の一意の番号を見るということです。または、このようなことが起こります。

  1. クライアント1クエリ。 self.paramは1ずつ増加します。
  2. Returnステートメントを実行する前に、スレッドはクライアント2に切り替えます。self.paramは再びインクリメントされます。
  3. スレッドはクライアント1に戻り、クライアントには2という数値が返されます。
  4. これで、スレッドはクライアント2に移動し、クライアント3に戻ります。

クライアントは2つしかなかったため、予想される結果は2と3ではなく1と2でした。数はスキップされました。

これは実際にアプリケーションをスケールアップすると起こりますか?グローバル変数の代替案は何ですか?

59
sayantankhan

グローバル変数を使用してこの種のデータを保持することはできません。スレッドセーフではないだけでなく、process安全でもないため、実稼働環境のWSGIサーバーは複数のプロセスを生成します。スレッドを使用してリクエストを処理している場合、カウントが間違っているだけでなく、リクエストを処理したプロセスによってもカウントが異なります。

Flaskの外部のデータソースを使用して、グローバルデータを保持します。データベース、memcached、またはredisはすべて、ニーズに応じて適切な個別のストレージエリアです。 Pythonデータをロードしてアクセスする必要がある場合は、 _multiprocessing.Manager_ を検討してください。セッションは、ユーザーごとの単純なデータにも使用できます。


開発サーバーは、単一のスレッドとプロセスで実行できます。各リクエストは同期的に処理されるため、説明した動作は表示されません。スレッドまたはプロセスを有効にすると、表示されます。 app.run(threaded=True)またはapp.run(processes=10)。 (1.0では、サーバーはデフォルトでスレッド化されています。)


一部のWSGIサーバーは、geventまたは別の非同期ワーカーをサポートする場合があります。大部分の競合状態に対する保護がないため、グローバル変数はまだスレッドセーフではありません。 1人のワーカーが値を取得し、譲り渡し、別のワーカーがそれを修正し、譲り、その後最初のワーカーもそれを修正するというシナリオがあります。


リクエスト中にグローバルデータを保存する必要がある場合、Flaskの gオブジェクト を使用できます。別の一般的なケースは、データベース接続を管理するトップレベルのオブジェクトです。このタイプの「グローバル」の違いは、それが各リクエストに固有であり、使用されないbetweenリクエストであり、セットアップとティアダウンを管理する何かがあることですリソース。

56
davidism

これは、実際にはグローバルのスレッドセーフに対する答えではありません。

しかし、ここでセッションに言及することが重要だと思います。クライアント固有のデータを保存する方法を探しています。すべての接続は、スレッドセーフな方法で、独自のデータプールにアクセスできる必要があります。

これはサーバー側のセッションで可能であり、非常にきれいなflask=プラグイン: https://pythonhosted.org/Flask-Session/

セッションをセットアップすると、session変数がすべてのルートで使用でき、辞書のように動作します。このディクショナリに保存されているデータは、接続しているクライアントごとに個別です。

これは短いデモです:

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

pip install Flask-Sessionの後、これを実行できるはずです。異なるブラウザーからアクセスしてみてください。カウンターがそれらの間で共有されていないことがわかります。

8
lhk