我有一个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
答案 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
以递增方式响应客户端请求。