6

我正在尝试编写一个烧瓶扩展,它需要在请求之间保留一些信息。当我使用单个进程运行 Werkzeug 时,这可以正常工作,但是当我使用多个进程运行时,我会遇到一些我不理解的奇怪行为。以这个简单的应用为例:

from flask import Flask
app = Flask(__name__)

class Counter(object):
    def __init__(self, app):
        print('initializing a Counter object')
        self.app = app
        self.value = 0

    def increment(self):
        self.value += 1
        print('Just incremented, current value is ', self.value)

counter = Counter(app)

@app.route('/')
def index():
    for i in range(4):
        counter.increment()
    return 'index'

if __name__ == '__main__':
    #scenario 1 - single process
    #app.run()
    #scenario 2 - threaded
    #app.run(threaded=True)
    #scenario 3 - two processes
    app.run(processes=2)

对于前两种情况,它的行为完全符合我的预期:Counter 对象被初始化一次,然后随着对“/”路由的每个请求而递增。当我使用第三个场景(传递进程=2)运行它时,我得到这个作为输出:

 initializing a Counter object
  * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 Just incremented, current value is  1
 Just incremented, current value is  2
 Just incremented, current value is  3
 Just incremented, current value is  4
 127.0.0.1 - - [30/Aug/2015 09:47:25] "GET / HTTP/1.1" 200 -
 Just incremented, current value is  1
 Just incremented, current value is  2
 Just incremented, current value is  3
 Just incremented, current value is  4
 127.0.0.1 - - [30/Aug/2015 09:47:26] "GET / HTTP/1.1" 200 -
 Just incremented, current value is  1
 Just incremented, current value is  2
 Just incremented, current value is  3
 Just incremented, current value is  4
 127.0.0.1 - - [30/Aug/2015 09:47:27] "GET / HTTP/1.1" 200 -

似乎 counter.value 在初始化后立即返回到它的状态,而实际上没有重新初始化。有人能解释一下 Werkzeug 在内部为实现这一目标所做的工作吗?我也非常有兴趣了解是否有一种方法可以使其行为符合我的天真期望(两个进程,每个进程都有自己的 Counter 实例)。谢谢!

4

1 回答 1

4

第一个示例(单线程)只使用了一个Counter,所以它可以工作。

第二个例子(多线程),产生线程来处理每个请求。Counter它们与生成之前创建的内存共享内存,因此从每个增量中递增它们会增加相同的内容。

最后一个示例(多个进程),生成进程来处理每个请求。 Flask 的开发服务器使用fork:每个孩子看到相同的起点(计数器已经初始化),但在他们自己的地址空间中递增,当请求结束时消失。

import os

class Counter:
    def __init__(self):
        print('init')
        self.value = 0

    def increment(self):
        self.value += 1
        print('inc -> {}'.format(self.value))

counter = Counter()

def multi():
    if not os.fork():
        # child starts with copy of parent memory
        for _ in range(3):
            # increments three times
            counter.increment()

        # child is done
        os._exit(0)

# three processes run
for _ in range(3):
    multi()
init
inc -> 1
inc -> 2
inc -> 3
inc -> 1
inc -> 2
inc -> 3
inc -> 1
inc -> 2
inc -> 3

使用数据库或其他外部存储跨进程存储全局状态,使用before_after_request. 请注意,这并不完全简单,因为您必须使每个请求线程安全地存储计数器的增量值,以便两个线程不会同时覆盖该值。

req 1 starts, gets stored value = 4
req 2 starts, gets stored value = 4
req 1 increments, value = 8
req 1 saves, value = 8
req 2 increments, value = 8
req 2 saves, value = 8 but should = 12
于 2015-08-30T17:51:59.860 回答