在 Flask 中全局变量是线程安全的吗? 我如何在请求之间共享数据?

在我的应用程序中,通过发出请求更改公共对象的状态,并且响应取决于状态。

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。

因为只有两个客户,所以预期的结果是1和2,而不是2和3。跳过了一个号码。

当我扩展应用程序时,这种情况真的会发生吗? 我应该考虑全局变量的替代品是什么?

72546 次浏览

不能使用全局变量来保存这类数据。它不仅不是线程安全的,也不是 程序安全的,而且生产中的 WSGI 服务器会产生多个进程。如果使用线程处理请求,计数不仅会出错,而且还会因处理请求的进程而有所不同。

使用 Flask 之外的数据源来保存全局数据。根据您的需要,数据库、 memcached 或 redis 都是适当的独立存储区域。如果需要加载和访问 Python 数据,请考虑 multiprocessing.Manager。您还可以将会话用于每个用户的简单数据。


开发服务器可以在单线程和进程中运行。您不会看到您所描述的行为,因为每个请求都将被同步处理。启用线程或进程,您将看到它。app.run(threaded=True)app.run(processes=10)。(在1.0中,服务器默认是线程化的。)


某些 WSGI 服务器可能支持 gevent 或其他异步 worker。全局变量仍然不是线程安全的,因为仍然没有针对大多数竞态条件的保护。您仍然可以有这样一个场景: 一个工作者获得一个值,生成,另一个工作者修改它,生成,然后第一个工作者也修改它。


如果需要存储一些全局数据 期间请求,可以使用 Flask 的 g物体。另一种常见的情况是一些管理数据库连接的顶级对象。这种类型的“全局”的区别在于它对于每个请求都是独一无二的,而不是使用的 中间请求,并且有一些东西可以管理资源的设置和拆分。

这实际上不是全局线程安全的答案。

但我认为在这里提到会议很重要。 您正在寻找存储特定于客户端的数据的方法。每个连接都应该以线程安全的方式访问自己的数据池。

服务器端会话可以做到这一点,它们可以在一个非常简洁的烧瓶插件 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之后,您应该能够运行这个。尝试从不同的浏览器访问它,您将看到计数器不是在它们之间共享的。

虽然完全接受以前的反对的答案,并劝阻使用全局变量的生产和可伸缩的烧瓶存储,为了原型或真正简单的服务器,运行在烧瓶’开发服务器’..。

...

Python 内置的数据类型,以及我个人使用和测试的全局 dict根据 Python 文档都是 线安全的。程序不安全。

在开发服务器下运行的每个(可能是并发的) Flask 会话中,来自这样一个(服务器全局) dict 的插入、查找和读取都是可以的。

当使用唯一的 Flask 会话密钥来键入这样一个全局 dict 时,它对于会话特定数据的服务器端存储非常有用,否则就不适合进入 cookie (最大大小为4kB)。

当然,这样一个服务器全局结果应该小心谨慎,因为它太大了,处于内存中。在请求处理期间,可以对某种过期的“旧”键/值对进行编码。

同样,不推荐用于生产部署或可伸缩部署,但对于本地面向任务的服务器来说,这样做可能没有问题,因为单独的数据库对于给定的任务来说太多了。

...

请求外部数据源的另一个示例是缓存,比如 烧瓶缓存或其他扩展提供的缓存。

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