Counting concurrently is hard. Assume the count is 0. If two users both hit the endpoint at close enough intervals, they may each get the value 0, increment it to 1, and put it back. Two users hit the endpoint, but the resulting count is 1, not 2. To get around this, you need to use a data store that supports incrementing atomically (as in, an operation that only one process can do at a time).
You can't use a simple Python global
because WSGI servers will spawn multiple processes, so they will each have their own independent copy of the global. Repeated requests could be handled by different processes, resulting in different, unsynchronized values.
The simplest solution is a Python multiprocessing.Value
. This synchronizes access to a shared value across processes, as long as the processes are spawned after the value is created.
from flask import Flask, jsonify
from multiprocessing import Value
counter = Value('i', 0)
app = Flask(__name__)
@app.route('/')
def index():
with counter.get_lock():
counter.value += 1
out = counter.value
return jsonify(count=out)
app.run(processes=8)
# access http://localhost:5000/ multiple times quickly, the count will be correct
There are still some caveats:
- The data only persists as long as the manager is alive. If you restart the server, the counter resets too.
- If the application processes are distributed across multiple machines, shared memory suffers the same issues as globals: they are only synchronized on the local machine, not across the network.
For real world scenarios, Redis is a much more robust solution. The server is independent of the web application, has options for persistence, and can do atomic increments. It can also be used for other parts of the application, such as caching.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…