[英]Is it safe to use global variables in flask?
假设我想要一个 flask 服务器运行相同的全局 state 可能通过请求修改。 也就是说,让最初的state
用数字5
表示。 通过调用/getn
,返回 state 并通过/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
,因为假设全局变量在 flask 中不是线程安全的。 但是,返回的 state 等于7
。
可以这样解释吗? 此外,执行此任务的正确方法是什么?
谢谢!
据我了解,当您运行 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 字节码指令的序列,并且如果线程在第三条和第四条指令之间被中断到另一个正在执行相同操作的线程完成,那么其中一个更新将丢失。 但是发生这种情况的可能性对于线程来说并不高,因为多个线程不会并行执行字节码,因为它们必须锁定全局解释器锁 (GIL),并且只有在它们的时间片过期或当它们 go 时才会失去对另一个线程的控制进入 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 端点,则 output应该是 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")
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.