在Tornado中缓存和重用函数结果

时间:2015-09-03 11:21:13

标签: python tornado

我有一个昂贵的功能,可以包含在我的Tornado应用程序中。功能 返回几个输出,但由于遗留原因,访问这些输出 分别通过不同的处理程序。

有没有办法只执行一次该函数,重复使用该结果 不同的处理程序并保留Tornado的异步行为?

from tornado.web import RequestHandler
from tonado.ioloop import IOLoop

# the expensive function
def add(x, y):
    z = x + y
    return x, y, z

# the handlers that reuse the function
class Get_X(RequestHandler):
    def get(self, x, y):
        x, y, z = add(x, y) 
        return x

class Get_Y(RequestHandler):
    def get(self, x, y):
        x, y, z = add(x, y) 
        return y

class Get_Z(RequestHandler):
    def get(self, x, y):
        x, y, z = add(x, y) 
        return z

# the web service
application = tornado.web.Application([
    (r'/Get_X', Get_X),
    (r'/Get_Y', Get_Y),
    (r'/Get_Z', Get_Z),
])

application.listen(8888)
IOLoop.current().start()

我考虑过在字典中缓存函数的结果,但是我不确定如何让其他两个处理程序等待,而第一个创建一个字典条目。

2 个答案:

答案 0 :(得分:9)

龙卷风Futures是可重复使用的,因此您可以在产生它之前保存Future。许多现成的缓存装饰器(如python 3.2的functools.lru_cache只有将它们放在@gen.coroutine前才会起作用:

import functools
from tornado import gen
from tornado.ioloop import IOLoop

@functools.lru_cache(maxsize=100)
@gen.coroutine
def expensive_function():
    print('starting expensive_function')
    yield gen.sleep(5)
    return 1, 2, 3

@gen.coroutine
def get_x():
    print('starting get_x')
    x, y, z = yield expensive_function()
    return x

@gen.coroutine
def get_y():
    print('starting get_y')
    x, y, z = yield expensive_function()
    return y

@gen.coroutine
def get_z():
    print('starting get_z')
    x, y, z = yield expensive_function()
    return z

@gen.coroutine
def main():
    x, y, z = yield [get_x(), get_y(), get_z()]
    print(x, y, z)

if __name__ == '__main__':
    IOLoop.current().run_sync(main)

打印:

starting get_x
starting expensive_function
starting get_y
starting get_z
finished expensive_function
1 2 3

答案 1 :(得分:3)

您关注一个处理程序需要时间来计算要放入缓存中的值,而其他处理程序则等待该值出现在缓存中。

Tornado 4.2包含一个Event类,可用于协调需要缓存值的协同程序。当处理程序想要从缓存中获取值时,它会检查缓存的值是否存在:

from tornado import locks

class Get_X(RequestHandler):
    @gen.coroutine
    def get(self, x, y):
        key = (x, y, 'Get_X')
        if key in cache:
            value = cache[key]
            if isinstance(value, locks.Event):
                # Another coroutine has begun calculating.
                yield value.wait()
                value = cache[key]

            self.write(value)
            return

        # Calculate the value.
        cache[key] = event = locks.Event()
        value = calculate(x, y)
        cache[key] = value
        event.set()
        self.write(value)

此代码未经测试。

在实际代码中,您应该在try /中包装calculate,除非在calculate失败时从缓存中清除事件。否则,所有其他协同程序将永远等待设置事件。

我假设calculate返回一个您可以传递给self.write的字符串。在您的应用程序中,可能会进一步处理该值,然后才能调用self.writeself.render

您还应该考虑缓存增长的大小:值有多大,以及有多少个不同的密钥?您可能需要一个有限的缓存来驱逐最近最少使用的值;有很多关于" Python LRU缓存的搜索结果",你可能try Raymond Hettinger's因为他受到广泛尊重。

有关使用事件围绕缓存进行同步的RequestHandler的更复杂示例,请参阅my proxy example in the Toro documentation。它远不是一个功能齐全的Web代理,但编写的示例是为了演示您提出的确切问题的解决方案:如何在计算要放入缓存中的值时避免重复工作。