Flask 中的全局变量是线程安全的吗?如何在请求之间共享数据?
- 2024-11-18 08:41:00
- admin 原创
- 13
问题描述:
在我的应用程序中,通过发出请求来改变普通对象的状态,并且响应取决于状态。
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 查询。
self.param
加 1。在执行返回语句之前,线程切换到客户端 2。
self.param
再次递增。线程切换回客户端 1,并向客户端返回数字 2。
现在线程转到客户端 2 并返回数字 3。
由于只有两个客户端,因此预期结果是 1 和 2,而不是 2 和 3。跳过了一个数字。
当我扩大应用程序规模时,这种情况真的会发生吗?我应该考虑全局变量的哪些替代方案?
解决方案 1:
您不能使用全局变量来保存此类数据。它不仅不是线程安全的,也不是进程安全的,而且生产中的 WSGI 服务器会产生多个进程。如果您使用线程来处理请求,您的计数不仅会出错,而且它们还会根据处理请求的进程而有所不同。
使用 Flask 之外的数据源来保存全局数据。数据库、memcached 或 redis 都是合适的单独存储区域,具体取决于您的需求。如果您需要加载和访问 Python 数据,请考虑multiprocessing.Manager
。您还可以使用会话来存储每个用户的简单数据。
开发服务器可能以单线程和单进程运行。由于每个请求都将同步处理,因此您不会看到您描述的行为。启用线程或进程,您将看到它。app.run(threaded=True)
或app.run(processes=10)
。(在 1.0 中,服务器默认是线程化的。)
一些 WSGI 服务器可能支持 gevent 或其他异步工作程序。全局变量仍然不是线程安全的,因为仍然没有针对大多数竞争条件的保护。您仍然可能遇到这样的情况:一个工作程序获取一个值,产生结果,另一个工作程序修改它,产生结果,然后第一个工作程序也修改它。
如果您需要在请求期间存储一些全局数据,则可以使用 Flask 的g
对象。另一个常见情况是管理数据库连接的顶级对象。这种“全局”对象的区别在于,它对于每个请求都是唯一的,不会在请求之间使用,并且有一些东西管理资源的设置和拆卸。
解决方案 2:
这实际上并不是对全局线程安全的答案。
但我认为在这里提到会话很重要。您正在寻找一种存储客户端特定数据的方法。每个连接都应该以线程安全的方式访问自己的数据池。
这可以通过服务器端会话来实现,它们可以在非常简洁的 Flask 插件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
,您应该能够运行它。尝试从不同的浏览器访问它,您会发现计数器在它们之间不共享。
解决方案 3:
请求外部数据源的另一个示例是缓存,例如Flask-Caching或其他扩展提供的缓存。
创建一个文件
common.py
并将以下内容放入其中:
from flask_caching import Cache
# Instantiate the cache
cache = Cache()
在创建您的文件中
flask app
,使用以下代码注册您的缓存:
# Import cache
from common import cache
# ...
app = Flask(__name__)
cache.init_app(app=app, config={"CACHE_TYPE": "filesystem",'CACHE_DIR': Path('/tmp')})
现在通过导入缓存并执行如下操作在整个应用程序中使用:
# 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")
解决方案 4:
虽然完全接受以前赞成的答案,并且不鼓励在生产和可扩展的 Flask 存储中使用全局变量,但为了原型设计或真正简单的服务器,在 Flask“开发服务器”下运行……
...
根据 Python 文档, Python 内置数据类型以及我个人使用和测试过的全局数据类型是线程dict
安全的。但不是进程安全的。
在开发服务器下运行的每个(可能是并发的)Flask 会话都可以进行此类(服务器全局)字典的插入、查找和读取。
当这样的全局字典以唯一的 Flask 会话密钥为关键字时,它对于服务器端存储会话特定数据非常有用,否则无法放入 cookie(最大大小为 4 kB)。
当然,这种服务器全局字典应小心谨慎,以免在内存中变得过大。可以在请求处理期间对“旧”键/值对的过期进行某种编码。
再次强调,我们并不推荐将其用于生产或可扩展部署,但是对于本地面向任务的服务器来说,使用单独的数据库来完成给定的任务来说可能没问题。
...
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件