Python 每次访问烧瓶视图的增量计数器

Python 每次访问烧瓶视图的增量计数器,python,flask,Python,Flask,我想在用户访问我的Flask应用程序中的页面时增加一个计数器。如果有两个用户访问该页面,则计数应增加2。我尝试了以下方法,但计数始终为1。如何增加每次访问的值 @app.route('/count') def make_count(): count = 0 value = count + 1 return jsonify(count=value) 同时计算是困难的。假设计数为0。如果两个用户都以足够近的间隔命中端点,他们可能各自得到值0,将其递增为1,然后将其放回。两个

我想在用户访问我的Flask应用程序中的页面时增加一个计数器。如果有两个用户访问该页面,则计数应增加2。我尝试了以下方法,但计数始终为1。如何增加每次访问的值

@app.route('/count')
def make_count():
    count = 0
    value = count + 1
    return jsonify(count=value)

同时计算是困难的。假设计数为0。如果两个用户都以足够近的间隔命中端点,他们可能各自得到值0,将其递增为1,然后将其放回。两个用户命中端点,但结果计数为1,而不是2。为了解决这个问题,您需要使用一个支持原子递增的数据存储(例如,一次只有一个进程可以执行的操作)

您不能使用简单的Python
global
,因为WSGI服务器将生成多个进程,因此它们每个都有自己的全局进程的独立副本。重复的请求可以由不同的进程处理,从而产生不同的、不同步的值

最简单的解决方案是Python。只要在创建了共享值之后生成了共享值,就可以跨进程同步对共享值的访问

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

还有一些警告:

  • 只有在管理器处于活动状态时,数据才会持续存在。如果重新启动服务器,计数器也会重置
  • 如果应用程序进程分布在多台机器上,那么共享内存会遇到与全局内存相同的问题:它们只在本地机器上同步,而不是在网络上同步

对于现实世界场景,这是一个更加健壮的解决方案。服务器独立于web应用程序,具有持久性选项,并且可以执行原子增量。它也可以用于应用程序的其他部分,如缓存。

在@davidism的公认答案中有一个小问题。
multiprocessing.Value
是在锁之外访问的,因此如果您运气不好,仍然有可能出现重复值

下面是一个显示碰撞的示例。它还显示了如果您使用异步代码(asyncio有自己的锁定机制),这种冲突是如何发生的

以下是上述结果的输出:

*** Showing that multiprocessing.Value is multiprocess safe ***
Testing concurrent returning inside of lock...
Returning value inside of lock context won't cause duplicates when using non-asyncronous executor
[1, 3, 2]
Testing concurrent returning outside lock...
Returning value outside of lock context can cause duplicate values
[4, 6, 6]
*** Showing that multiprocessing.Value is not async safe ***
Testing async returning outside of lock...
[8, 9, 9]
Testing async returning inside of lock...
[11, 12, 12]
幸运的是,您使用的是同步的Flask,因此异步问题与您的用例无关

因此,我建议更改已接受的答案,将锁存储在上下文中,然后尽快释放锁。如果要调用jsonify或其他任何东西,则在执行不需要锁的操作时会保留锁

@app.route('/')
def index():
    with counter.get_lock():
        counter.value += 1
        # save the value ASAP rather than passing to jsonify
        # to keep lock time short
        unique_count = counter.value

    return jsonify(count=unique_count)
@app.route('/')
def index():
    with counter.get_lock():
        counter.value += 1
        # save the value ASAP rather than passing to jsonify
        # to keep lock time short
        unique_count = counter.value

    return jsonify(count=unique_count)