全局变量在Flask中是否安全?如何在请求之间共享数据?

2024-04-26 19:14:52 发布

您现在位置:Python中文网/ 问答频道 /正文

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

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。跳过了一个数字。

当我扩展应用程序时,这真的会发生吗?除了全局变量,我还应该考虑哪些替代方案?


Tags: selfobj应用程序客户机客户returnparam状态
2条回答

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

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


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


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


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

这并不是解决全局线程安全问题的真正方法。

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

这在服务器端会话中是可能的,它们可以在一个非常整洁的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 __name__ == '__main__':
    app.run()

pip install Flask-Session之后,您应该能够运行这个。尝试从不同的浏览器访问它,您将看到计数器在它们之间不共享。

相关问题 更多 >