从Tornado的ProcessPoolExecutor收集增量结果

时间:2017-02-21 16:51:59

标签: python multiprocessing tornado blinker

我有一个tornado应用程序需要在ProcessPoolExecutor上运行阻止功能。此阻止功能使用一个库,该库通过blinker事件发出增量结果。我想收集这些活动并在发生时将其发送回我的tornado应用。

首先,tornado似乎是这个用例的理想选择,因为它是异步的。我以为我可以简单地将tornado.queues.Queue对象传递给要在池上运行的函数,然后将put()个事件作为blinker事件回调的一部分传递到此队列。

但是,在阅读tornado.queues.Queue的文档时,我了解到它们不是像multiprocessing.Queue这样的流程进行管理而且不是线程安全的。

有没有办法从pool发现这些事件?我应该包裹multiprocessing.Queue以便生成Futures吗?这似乎不太可行,因为我怀疑multiprocessing的内部与tornado兼容。

[编辑] 这里有一些好的线索:https://gist.github.com/hoffrocket/8050711

2 个答案:

答案 0 :(得分:1)

要收集传递给ProcessPoolExecutor的任务的返回值以外的任何内容,您必须使用multiprocessing.Queue(或multiprocessing库中的其他对象)。然后,由于multiprocessing.Queue仅公开同步接口,您必须使用父进程中的另一个线程从队列中读取(不涉及实现细节。这里有一个可以使用的文件描述符,但是我们& #39;我现在忽略它,因为它没有记录,可能会有变化。)

这是一个未经测试的快速示例:

queue = multiprocessing.Queue()
proc_pool = concurrent.futures.ProcessPoolExecutor()
thread_pool = concurrent.futures.ThreadPoolExecutor()

async def read_events():
    while True:
        event = await thread_pool.submit(queue.get)
        print(event)

async def foo():
    IOLoop.current.spawn_callback(read_events)
    await proc_pool.submit(do_something_and_write_to_queue)

答案 1 :(得分:0)

你可以更简单地做到这一点。这是一个协程,它向子进程提交了四个慢速函数调用并等待它们:

from concurrent.futures import ProcessPoolExecutor
from time import sleep

from tornado import gen, ioloop

pool = ProcessPoolExecutor()


def calculate_slowly(x):
    sleep(x)
    return x


async def parallel_tasks():
    # Create futures in a randomized order.
    futures = [gen.convert_yielded(pool.submit(calculate_slowly, i))
               for i in [1, 3, 2, 4]]

    wait_iterator = gen.WaitIterator(*futures)
    while not wait_iterator.done():
        try:
            result = await wait_iterator.next()
        except Exception as e:
            print("Error {} from {}".format(e, wait_iterator.current_future))
        else:
            print("Result {} received from future number {}".format(
                result, wait_iterator.current_index))


ioloop.IOLoop.current().run_sync(parallel_tasks)

输出:

Result 1 received from future number 0
Result 2 received from future number 2
Result 3 received from future number 1
Result 4 received from future number 3

你可以看到协同程序按照它们完成的顺序收到结果,而不是它们提交的顺序:未来的数字1在未来的数字2后解析,因为未来的数字1会睡得更久。 convert_yielded将ProcessPoolExecutor返回的Futures转换为可以在协程中等待的Tornado兼容的Futures。

每个future将解析为calculate_slowly返回的值:在这种情况下,它与传递给calculate_slowly的数字相同,并且与calculate_slowly睡眠的秒数相同。

要将其包含在RequestHandler中,请尝试以下方法:

class MainHandler(web.RequestHandler):
    async def get(self):
        self.write("Starting....\n")
        self.flush()

        futures = [gen.convert_yielded(pool.submit(calculate_slowly, i))
                   for i in [1, 3, 2, 4]]

        wait_iterator = gen.WaitIterator(*futures)
        while not wait_iterator.done():
            result = await wait_iterator.next()
            self.write("Result {} received from future number {}\n".format(
                result, wait_iterator.current_index))

            self.flush()


if __name__ == "__main__":
    application = web.Application([
        (r"/", MainHandler),
    ])
    application.listen(8888)
    ioloop.IOLoop.instance().start()

您可以观察服务器是否curl localhost:8888以递增方式响应客户端请求。