【问题标题】:Caching and reusing a function result in Tornado在 Tornado 中缓存和重用函数结果
【发布时间】:2015-11-29 04:57:02
【问题描述】:

我的 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()

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

【问题讨论】:

    标签: python tornado


    【解决方案1】:

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

    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)
    

    此代码未经测试。

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

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

    您还应该考虑缓存可能会增长到多大:值有多大,会有多少不同的键?您可能需要一个有界缓存来驱逐最近最少使用的值; “Python LRU 缓存”有很多搜索结果,您可能会try Raymond Hettinger's,因为他受到广泛尊重。

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

    【讨论】:

      【解决方案2】:

      Tornado 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
      

      【讨论】:

      • 嗨,本,您的回答看起来是我正在寻找的一个很好的解决方案。我已经发布了一个问题here,如果你能看看并分享你的想法,我会很高兴。谢谢!
      猜你喜欢
      • 2010-11-13
      • 1970-01-01
      • 1970-01-01
      • 2011-01-14
      • 2011-01-18
      • 2019-01-21
      • 2021-10-05
      • 1970-01-01
      • 2010-10-12
      相关资源
      最近更新 更多