为什么 time.sleep 运行 在 Tornado 协程中不并行?
Why doesn't time.sleep run in parallel in a Tornado coroutine?
当我在一个简单的 Tornado 应用程序中 运行 这个处理程序并使用 curl
向它发出两个请求时,它不会 运行 并行。它打印出“1 2 3 4 5 1 2 3 4 5”,当我想要它打印“1 1 2 2 3 3 4 4 5 5”时。
class SleepHandler(RequestHandler):
def get(self):
for i in range(5):
print(i)
time.sleep(1)
我做错了什么?
原因是 time.sleep
是一个 阻塞 函数:它不允许控制 return 到 IOLoop
这样其他处理程序就可以 运行。
当然,time.sleep
在这些示例中通常只是一个占位符,重点是展示当处理程序中的某些内容变慢时会发生什么。无论实际代码在做什么,要实现并发,阻塞代码都必须替换为非阻塞等效代码。这意味着三件事之一:
寻找协程友好的等价物。对于 time.sleep,请改用 tornado.gen.sleep:
class CoroutineSleepHandler(RequestHandler):
@gen.coroutine
def get(self):
for i in range(5):
print(i)
yield gen.sleep(1)
当此选项可用时,它通常是最好的方法。请参阅 Tornado wiki 以获取可能有用的异步库链接。
查找基于回调的等价物。与第一个选项类似,基于回调的库可用于许多任务,尽管它们比为协程设计的库使用起来稍微复杂一些。这些通常与 tornado.gen.Task
一起用作适配器:
class CoroutineTimeoutHandler(RequestHandler):
@gen.coroutine
def get(self):
io_loop = IOLoop.current()
for i in range(5):
print(i)
yield gen.Task(io_loop.add_timeout, io_loop.time() + 1)
同样,Tornado wiki 可用于查找合适的库。
运行 另一个线程上的阻塞代码。当异步库不可用时,concurrent.futures.ThreadPoolExecutor
可用于 运行 另一个线程上的任何阻塞代码。这是一个通用的解决方案,可用于任何阻塞函数,无论是否存在异步副本:
executor = concurrent.futures.ThreadPoolExecutor(8)
class ThreadPoolHandler(RequestHandler):
@gen.coroutine
def get(self):
for i in range(5):
print(i)
yield executor.submit(time.sleep, 1)
有关阻塞和异步函数的更多信息,请参阅 Tornado 用户指南的 Asynchronous I/O chapter。
当我在一个简单的 Tornado 应用程序中 运行 这个处理程序并使用 curl
向它发出两个请求时,它不会 运行 并行。它打印出“1 2 3 4 5 1 2 3 4 5”,当我想要它打印“1 1 2 2 3 3 4 4 5 5”时。
class SleepHandler(RequestHandler):
def get(self):
for i in range(5):
print(i)
time.sleep(1)
我做错了什么?
原因是 time.sleep
是一个 阻塞 函数:它不允许控制 return 到 IOLoop
这样其他处理程序就可以 运行。
当然,time.sleep
在这些示例中通常只是一个占位符,重点是展示当处理程序中的某些内容变慢时会发生什么。无论实际代码在做什么,要实现并发,阻塞代码都必须替换为非阻塞等效代码。这意味着三件事之一:
寻找协程友好的等价物。对于 time.sleep,请改用 tornado.gen.sleep:
class CoroutineSleepHandler(RequestHandler): @gen.coroutine def get(self): for i in range(5): print(i) yield gen.sleep(1)
当此选项可用时,它通常是最好的方法。请参阅 Tornado wiki 以获取可能有用的异步库链接。
查找基于回调的等价物。与第一个选项类似,基于回调的库可用于许多任务,尽管它们比为协程设计的库使用起来稍微复杂一些。这些通常与
tornado.gen.Task
一起用作适配器:class CoroutineTimeoutHandler(RequestHandler): @gen.coroutine def get(self): io_loop = IOLoop.current() for i in range(5): print(i) yield gen.Task(io_loop.add_timeout, io_loop.time() + 1)
同样,Tornado wiki 可用于查找合适的库。
运行 另一个线程上的阻塞代码。当异步库不可用时,
concurrent.futures.ThreadPoolExecutor
可用于 运行 另一个线程上的任何阻塞代码。这是一个通用的解决方案,可用于任何阻塞函数,无论是否存在异步副本:executor = concurrent.futures.ThreadPoolExecutor(8) class ThreadPoolHandler(RequestHandler): @gen.coroutine def get(self): for i in range(5): print(i) yield executor.submit(time.sleep, 1)
有关阻塞和异步函数的更多信息,请参阅 Tornado 用户指南的 Asynchronous I/O chapter。