简体   繁体   English

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

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

In my application, the state of a common object is changed by making requests, and the response depends on the state.在我的应用中,一个普通的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')

If I run this on my development server, I expect to get 1, 2, 3 and so on.如果我在我的开发服务器上运行它,我希望得到 1、2、3 等等。 If requests are made from 100 different clients simultaneously, can something go wrong?如果同时从 100 个不同的客户端发出请求,那么 go 会不会出错? The expected result would be that the 100 different clients each see a unique number from 1 to 100. Or will something like this happen:预期的结果是 100 个不同的客户端每个看到一个从 1 到 100 的唯一数字。或者会发生这样的事情:

  1. Client 1 queries.客户端 1 查询。 self.param is incremented by 1. self.param增加 1。
  2. Before the return statement can be executed, the thread switches over to client 2. self.param is incremented again.在 return 语句可以执行之前,线程切换到客户端self.param再次递增。
  3. The thread switches back to client 1, and the client is returned the number 2, say.线程切换回客户端 1,客户端返回数字 2,比方说。
  4. Now the thread moves to client 2 and returns him/her the number 3.现在线程移动到客户端 2 并返回他/她的数字 3。

Since there were only two clients, the expected results were 1 and 2, not 2 and 3. A number was skipped.由于只有两个客户端,预期结果是 1 和 2,而不是 2 和 3。跳过了一个数字。

Will this actually happen as I scale up my application?当我扩展我的应用程序时,这真的会发生吗? What alternatives to a global variable should I look at?我应该查看全局变量的哪些替代方案?

You can't use global variables to hold this sort of data.您不能使用全局变量来保存此类数据。 Not only is it not thread safe, it's not process safe, and WSGI servers in production spawn multiple processes.它不仅不是线程安全的,也不是进程安全的,而且生产环境中的 WSGI 服务器会产生多个进程。 Not only would your counts be wrong if you were using threads to handle requests, they would also vary depending on which process handled the request.如果您使用线程来处理请求,您的计数不仅会出错,而且还会根据处理请求的进程而有所不同。

Use a data source outside of Flask to hold global data.使用 Flask 之外的数据源来保存全局数据。 A database, memcached, or redis are all appropriate separate storage areas, depending on your needs.数据库、memcached 或 redis 都是合适的单独存储区域,具体取决于您的需要。 If you need to load and access Python data, consider multiprocessing.Manager .如果您需要加载和访问 Python 数据,请考虑multiprocessing.Manager You could also use the session for simple data that is per-user.您还可以将会话用于每个用户的简单数据。


The development server may run in single thread and process.开发服务器可以在单线程和进程中运行。 You won't see the behavior you describe since each request will be handled synchronously.您不会看到您描述的行为,因为每个请求都将被同步处理。 Enable threads or processes and you will see it.启用线程或进程,您将看到它。 app.run(threaded=True) or app.run(processes=10) . app.run(threaded=True)app.run(processes=10) (In 1.0 the server is threaded by default.) (在 1.0 中,服务器默认是线程化的。)


Some WSGI servers may support gevent or another async worker.一些 WSGI 服务器可能支持 gevent 或其他异步工作者。 Global variables are still not thread safe because there's still no protection against most race conditions.全局变量仍然不是线程安全的,因为仍然没有针对大多数竞争条件的保护。 You can still have a scenario where one worker gets a value, yields, another modifies it, yields, then the first worker also modifies it.您仍然可以有一个场景,一个工人获得一个价值,产出,另一个修改它,产出,然后第一个工人也修改它。


If you need to store some global data during a request, you may use Flask's g object .如果您需要在请求期间存储一些全局数据,您可以使用 Flask 的g object Another common case is some top-level object that manages database connections.另一个常见的情况是一些管理数据库连接的顶级对象。 The distinction for this type of "global" is that it's unique to each request, not used between requests, and there's something managing the set up and teardown of the resource.这种“全局”类型的区别在于它对每个请求都是唯一的,而不是在请求之间使用,并且有一些东西可以管理资源的设置和拆卸。

This is not really an answer to thread safety of globals.这并不是对全局线程安全的真正答案。

But I think it is important to mention sessions here.但我认为在这里提及会议很重要。 You are looking for a way to store client-specific data.您正在寻找一种存储客户特定数据的方法。 Every connection should have access to its own pool of data, in a threadsafe way.每个连接都应该以线程安全的方式访问自己的数据池。

This is possible with server-side sessions, and they are available in a very neat flask plugin: https://pythonhosted.org/Flask-Session/这在服务器端会话中是可能的,它们可以在一个非常简洁的烧瓶插件中使用: https ://pythonhosted.org/Flask-Session/

If you set up sessions, a session variable is available in all your routes and it behaves like a dictionary.如果您设置会话,则session变量在您的所有路由中都可用,它的行为类似于字典。 The data stored in this dictionary is individual for each connecting client.存储在此字典中的数据对于每个连接的客户端都是单独的。

Here is a short demo:这是一个简短的演示:

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

After pip install Flask-Session , you should be able to run this.pip install Flask-Session之后,你应该可以运行它了。 Try accessing it from different browsers, you'll see that the counter is not shared between them.尝试从不同的浏览器访问它,您会发现它们之间没有共享计数器。

Another example of a data source external to requests is a cache, such as what's provided by Flask-Caching or another extension.请求外部数据源的另一个示例是缓存,例如Flask-Caching或其他扩展提供的缓存。

  1. Create a file common.py and place in it the following:创建一个文件common.py并在其中放置以下内容:
from flask_caching import Cache

# Instantiate the cache
cache = Cache()
  1. In the file where your flask app is created, register your cache with the following code:在创建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. Now use throughout your application by importing the cache and executing as follows:现在通过导入缓存并执行如下方式在整个应用程序中使用:
# 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")

While totally accepting the previous upvoted answers, and discouraging use of global variables for production and scalable Flask storage, for the purpose of prototyping or really simple servers, running under the flask 'development server'...虽然完全接受以前的赞成答案,并且不鼓励将全局变量用于生产和可扩展的 Flask 存储,但为了原型或非常简单的服务器,在 Flask '开发服务器'下运行......

... ...

The Python built-in data types, and I personally used and tested the global dict , as per Python documentation are thread safe. Python 内置数据类型,我个人使用并测试了全局dict根据 Python 文档线程安全的。 Not process safe.过程不安全。

The insertions, lookups, and reads from such a (server global) dict will be OK from each (possibly concurrent) Flask session running under the development server.在开发服务器下运行的每个(可能是并发的)Flask 会话中,从这样的(服务器全局)dict 中插入、查找和读取都可以。

When such a global dict is keyed with a unique Flask session key, it can be rather useful for server-side storage of session specific data otherwise not fitting into the cookie (max size 4 kB).当这样的全局 dict 使用唯一的 Flask 会话密钥作为密钥时,它对于会话特定数据的服务器端存储非常有用,否则不适合 cookie(最大大小 4 kB)。

Of course, such a server global dict should be carefully guarded for growing too large, being in-memory.当然,应该小心保护这样的服务器全局字典,以免变得太大,在内存中。 Some sort of expiring the 'old' key/value pairs can be coded during request processing.可以在请求处理期间对某种过期的“旧”键/值对进行编码。

Again, it is not recommended for production or scalable deployments, but it is possibly OK for local task-oriented servers where a separate database is too much for the given task.同样,不建议将其用于生产或可扩展部署,但对于本地面向任务的服务器来说可能没问题,因为单独的数据库对于给定任务来说太多了。

... ...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM