【问题标题】:Is it safe to use global variables in flask?在烧瓶中使用全局变量是否安全?
【发布时间】:2022-01-12 21:11:46
【问题描述】:

假设我想让一个烧瓶服务器以相同的全局状态运行,该状态可能会通过请求进行修改。 也就是说,让初始的state 由数字5 表示。通过调用/getn,返回状态,通过/inc增加状态。

from flask import Flask
from flask import request
import time
import os
app = Flask(__name__)

state = 5

@app.route('/inc')
def inc():
    global state
    print("sleep")
    time.sleep(5)
    state += 1
    return 'done'

@app.route('/getn')
def getn():
    global state
    return f"{state}"



if __name__ == '__main__':
    app.run(threaded=True, host="0.0.0.0")

现在我同时从两个不同的终端呼叫curl http://127.0.0.1:5000/inc。在阅读了相同的博客后,我预计在完成两个调用后,/getn 会给我号码6,因为假设全局变量在烧瓶中不是线程安全的。但是,返回的状态等于7

可以。解释这个?此外,执行此任务的正确方法是什么?

谢谢!

【问题讨论】:

  • 仅供参考:在任何类型的软件中使用全局变量会使您的代码更难测试,也更难更改您的代码(如果需要更改的话)。当可以安全使用它们时,避免它们几乎总是更聪明stackoverflow.com/q/10525582/801894

标签: python multithreading flask global-variables


【解决方案1】:

据我了解,当您运行 Flash 线程时,state += 1 语句存在潜在问题,其中state 是一个全局语句,因为该语句转换为字节码 变成:

0 LOAD_GLOBAL              0 (state)
2 LOAD_CONST               1 (1)
4 INPLACE_ADD
6 STORE_GLOBAL             0 (state)

让我们假设state 最初是 5。现在如果第一个线程执行上面的前三个指令并计算一个新值 6 但在它有机会将这个新值存储回state 之前会发生什么使用第四条指令,线程失去了对第二条线程的控制?因此,第二个线程从state 加载相同的值5,重新计算state 的新值,即6,并将其存储出来。现在,当第一个线程重新获得控制权时,它开始执行第四条字节码指令并再次存储 6。

因此,问题在于 += 运算符不是 原子,即它是作为 Python 字节码指令序列实现的,如果线程在第 3 条和第 4 条指令之间中断另一个线程正在执行完成的相同操作,则其中一个更新将丢失。但是这种情况发生的概率对于线程来说并没有那么高,因为多个线程不会并行执行字节码,因为它们必须锁定全局解释器锁 (GIL),并且只有在它们的时间片过期或它们离开时才会失去对另一个线程的控制进入 I/O 等待,此处不适用。

但为了绝对安全,您可以而且应该在锁的控制下进行更新。

from flask import Flask
from flask import request
import time
import os
from threading import Lock


app = Flask(__name__)

state_lock = Lock()
state = 5

@app.route('/inc')
def inc():
    global state
    print("sleep")
    time.sleep(5)
    with state_lock:
        state += 1
    return 'done'

@app.route('/getn')
def getn():
    # The global statement is not really required
    # since state is read/only, but it's okay:
    global state
    return f"{state}"



if __name__ == '__main__':
    app.run(threaded=True, host="0.0.0.0")

这里有一个更好的例子来说明递增全局不是线程安全的。如果您运行 /inc 端点的两个并发调用并调用 /getn 端点,输出应该是 200000000 但现在很有可能一个线程将失去其时间片一个或多个重要的时候,结果将不正确。然后在 with state_lock: 块内完成递增重试。

from flask import Flask
from threading import Lock


app = Flask(__name__)

state_lock = Lock()
state = 0

@app.route('/inc')
def inc():
    global state
    for _ in range(100_000_000):
        state += 1
    return 'done'

@app.route('/getn')
def getn():
    return f"{state}"



if __name__ == '__main__':
    app.run(threaded=True, host="0.0.0.0")

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-11-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多