簡體   English   中英

Flask 中的全局變量是線程安全的嗎? 如何在請求之間共享數據?

[英]Are global variables thread-safe in Flask? How do I share data between requests?

在我的應用中,一個普通的object的state是通過請求改變的,響應依賴於state。

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 個不同的客戶端發出請求,那么 go 會不會出錯? 預期的結果是 100 個不同的客戶端每個看到一個從 1 到 100 的唯一數字。或者會發生這樣的事情:

  1. 客戶端 1 查詢。 self.param增加 1。
  2. 在 return 語句可以執行之前,線程切換到客戶端self.param再次遞增。
  3. 線程切換回客戶端 1,客戶端返回數字 2,比方說。
  4. 現在線程移動到客戶端 2 並返回他/她的數字 3。

由於只有兩個客戶端,預期結果是 1 和 2,而不是 2 和 3。跳過了一個數字。

當我擴展我的應用程序時,這真的會發生嗎? 我應該查看全局變量的哪些替代方案?

您不能使用全局變量來保存此類數據。 它不僅不是線程安全的,也不是進程安全的,而且生產環境中的 WSGI 服務器會產生多個進程。 如果您使用線程來處理請求,您的計數不僅會出錯,而且還會根據處理請求的進程而有所不同。

使用 Flask 之外的數據源來保存全局數據。 數據庫、memcached 或 redis 都是合適的單獨存儲區域,具體取決於您的需要。 如果您需要加載和訪問 Python 數據,請考慮multiprocessing.Manager 您還可以將會話用於每個用戶的簡單數據。


開發服務器可以在單線程和進程中運行。 您不會看到您描述的行為,因為每個請求都將被同步處理。 啟用線程或進程,您將看到它。 app.run(threaded=True)app.run(processes=10) (在 1.0 中,服務器默認是線程化的。)


一些 WSGI 服務器可能支持 gevent 或其他異步工作者。 全局變量仍然不是線程安全的,因為仍然沒有針對大多數競爭條件的保護。 您仍然可以有一個場景,一個工人獲得一個價值,產出,另一個修改它,產出,然后第一個工人也修改它。


如果您需要在請求期間存儲一些全局數據,您可以使用 Flask 的g object 另一個常見的情況是一些管理數據庫連接的頂級對象。 這種“全局”類型的區別在於它對每個請求都是唯一的,而不是在請求之間使用,並且有一些東西可以管理資源的設置和拆卸。

這並不是對全局線程安全的真正答案。

但我認為在這里提及會議很重要。 您正在尋找一種存儲客戶特定數據的方法。 每個連接都應該以線程安全的方式訪問自己的數據池。

這在服務器端會話中是可能的,它們可以在一個非常簡潔的燒瓶插件中使用: 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 __name__ == '__main__':
    app.run()

pip install Flask-Session之后,你應該可以運行它了。 嘗試從不同的瀏覽器訪問它,您會發現它們之間沒有共享計數器。

請求外部數據源的另一個示例是緩存,例如Flask-Caching或其他擴展提供的緩存。

  1. 創建一個文件common.py並在其中放置以下內容:
from flask_caching import Cache

# Instantiate the cache
cache = Cache()
  1. 在創建flask app的文件中,使用以下代碼注冊緩存:
# Import cache
from common import cache

# ...
app = Flask(__name__)

cache.init_app(app=app, config={"CACHE_TYPE": "filesystem",'CACHE_DIR': Path('/tmp')})
  1. 現在通過導入緩存並執行如下方式在整個應用程序中使用:
# 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")

雖然完全接受以前的贊成答案,並且不鼓勵將全局變量用於生產和可擴展的 Flask 存儲,但為了原型或非常簡單的服務器,在 Flask '開發服務器'下運行......

...

Python 內置數據類型,我個人使用並測試了全局dict根據 Python 文檔線程安全的。 過程不安全。

在開發服務器下運行的每個(可能是並發的)Flask 會話中,從這樣的(服務器全局)dict 中插入、查找和讀取都可以。

當這樣的全局 dict 使用唯一的 Flask 會話密鑰作為密鑰時,它對於會話特定數據的服務器端存儲非常有用,否則不適合 cookie(最大大小 4 kB)。

當然,應該小心保護這樣的服務器全局字典,以免變得太大,在內存中。 可以在請求處理期間對某種過期的“舊”鍵/值對進行編碼。

同樣,不建議將其用於生產或可擴展部署,但對於本地面向任務的服務器來說可能沒問題,因為單獨的數據庫對於給定任務來說太多了。

...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM