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

say*_*han 70 python thread-safety 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')
Run Code Online (Sandbox Code Playgroud)

如果我在我的开发服务器上运行它,我希望得到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.跳过了一个数字.

当我扩展我的应用程序时,这会发生吗?我应该看一下全局变量的替代方案?

dav*_*ism 64

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

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


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


某些WSGI服务器可能支持gevent或其他异步工作程序.全局变量仍然不是线程安全的,因为仍然没有针对大多数竞争条件的保护.您仍然可以有一个方案,其中一个工人获得一个值,产量,另一个工人获得它,产量,然后第一个工人也修改它.


如果您需要请求期间存储一些全局数据,则可以使用Flask的g对象.另一种常见情况是一些管理数据库连接的顶级对象.这种"全局"的区别在于它对每个请求都是唯一的,不是请求之间使用,而是管理资源的设置和拆除.


R. *_*mac 15

虽然完全接受之前赞成的答案,并且不鼓励使用全局变量进行生产和可扩展的 Flask 存储,以实现原型设计或非常简单的服务器,在 Flask“开发服务器”下运行......

...

Python 内置数据类型,我个人使用并测试了 global dict根据 Python 文档,它是线程安全的。过程不安全。

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

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

当然,这样的服务器全局字典应该小心保护,以免在内存中变得太大。可以在请求处理期间对某种即将到期的“旧”键/值对进行编码。

同样,不建议将其用于生产或可扩展部署,但对于本地面向任务的服务器来说可能是可以的,因为单独的数据库对于给定的任务来说太多了。

...


lhk*_*lhk 11

这并不是对全局变量线程安全的真正答案。

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

服务器端会话可以做到这一点,它们可以在非常整齐的烧瓶插件中找到: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()
Run Code Online (Sandbox Code Playgroud)

之后pip install Flask-Session,您应该可以运行它。尝试从不同的浏览器访问它,您会发现计数器未在它们之间共享。


Yaa*_*ler 7

请求外部数据源的另一个示例是缓存,例如Flask-Caching或其他扩展提供的缓存

  1. 创建一个文件common.py并将以下内容放入其中:
from flask_caching import Cache

# Instantiate the cache
cache = Cache()
Run Code Online (Sandbox Code Playgroud)
  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')})
Run Code Online (Sandbox Code Playgroud)
  1. 现在通过导入缓存并按如下方式在整个应用程序中使用:
from flask_caching import Cache

# Instantiate the cache
cache = Cache()
Run Code Online (Sandbox Code Playgroud)