结合龙卷风gen.coroutine和joblib mem.cache装饰器

时间:2016-04-17 18:44:02

标签: asynchronous tornado coroutine joblib

想象一下,我们希望在Tornado应用程序上下文中异步执行一个处理繁重计算任务的函数。此外,我们想通过将其结果存储到磁盘来懒惰地评估函数,而不是为相同的参数重新运行该函数两次。

如果没有缓存结果(memoization),可以执行以下操作:

def complex_computation(arguments):
    ...
    return result

@gen.coroutine
def complex_computation_caller(arguments):
    ...
    result = complex_computation(arguments)
    raise gen.Return(result)

假设要实现功能记忆,我们从 joblib 中选择记忆类。通过简单地用@mem.cache修饰函数,可以很容易地记住函数:

@mem.cache
def complex_computation(arguments):
    ...
    return result

其中mem可以是mem = Memory(cachedir=get_cache_dir())

现在考虑将两者结合起来,我们在执行器上执行计算复杂的函数:

class TaskRunner(object):
    def __init__(self, loop=None, number_of_workers=1):
        self.executor = futures.ThreadPoolExecutor(number_of_workers)
        self.loop = loop or IOLoop.instance()

    @run_on_executor
    def run(self, func, *args, **kwargs):
        return func(*args, **kwargs)

mem = Memory(cachedir=get_cache_dir())
_runner = TaskRunner(1)

@mem.cache
def complex_computation(arguments):
    ...
    return result

@gen.coroutine
def complex_computation_caller(arguments):
    result = yield _runner.run(complex_computation, arguments)
    ...
    raise gen.Return(result)

所以第一个问题是上述方法在技术上是否正确?

现在让我们考虑以下情况:

@gen.coroutine
def first_coroutine(arguments):
    ...
    result = yield second_coroutine(arguments)
    raise gen.Return(result)

@gen.coroutine
def second_coroutine(arguments):
    ...
    result = yield third_coroutine(arguments)
    raise gen.Return(result)

第二个问题是如何记住second_coroutine?做以下事情是否正确:

@gen.coroutine
def first_coroutine(arguments):
    ...
    mem = Memory(cachedir=get_cache_dir())
    mem_second_coroutine = mem(second_coroutine)
    result = yield mem_second_coroutine(arguments)
    raise gen.Return(result)

@gen.coroutine
def second_coroutine(arguments):
    ...
    result = yield third_coroutine(arguments)
    raise gen.Return(result)

[更新I] Caching and reusing a function result in Tornado讨论了使用functools.lru_cacherepoze.lru.lru_cache作为第二个问题的解决方案。

1 个答案:

答案 0 :(得分:1)

Tornado协同程序返回的Future对象是可重用的,因此它通常可以使用内存缓存,例如functools.lru_cache,如this question中所述。请务必将缓存装饰器放在@gen.coroutine之前。

磁盘缓存(cachedir的{​​{1}}参数似乎暗示)是比较棘手的,因为Memory对象通常不能写入磁盘。您的Future示例应该有效,但它与其他人的做法根本不同,因为TaskRunner不是协程。您的上一个示例无效,因为它试图将complex_calculation对象放入缓存中。

相反,如果你想用装饰器缓存东西,你需要一个用第二个协程包装内部协程的装饰器。像这样:

Future