As I understand it, when you are running Flash threaded you have a potential issue with the statement state += 1
where state
is a global since that statement when converted to bytecode becomes:
0 LOAD_GLOBAL 0 (state)
2 LOAD_CONST 1 (1)
4 INPLACE_ADD
6 STORE_GLOBAL 0 (state)
Let's assume state
is initially 5. Now what would happen if the first thread executes the first three instructions above and computes a new value of 6 but before it has a chance to store this new value back out to state
with the fourth instruction, the thread loses control to the second thread? The second thread therefore loads the same value of 5 from state
, re-computes a new value of state
, namely 6, and stores it out. Now when the first thread regains control it gets to execute the fourth bytecode instruction and stores out 6 again.
The problem, therefore, is that the +=
operator is not atomic, i.e. it is implemented as a sequence of Python bytecode instructions and if a thread gets interrupted between the 3rd and 4th instruction to another thread that is executing the same operation that completes, then one of the updates will be lost. But the probability of this occurring is not that high with threading since multiple threads do not execute bytecode in parallel since they must lock on the Global Interpreter Lock (GIL) and only lose control to another thread when their time slices have expired or when they go into an I/O wait, which does not apply here.
But to be perfectly safe, you can and should do the update under control of a lock.
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")
Here is a better example to show that incrementing a global is not thread safe. If you run two concurrent invocations of the /inc endpoint and the invoke the /getn endpoint, the output should be 200000000 but now there is a very good chance that a thread will lose its time slice one or more times when it matters and the result will not be correct. Then retry it with the incrementing done within a with state_lock:
block.
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")