When I run this handler in a simple Tornado app and make two requests to it with curl
, it doesn't run in parallel. It prints out "1 2 3 4 5 1 2 3 4 5", when I want it to print "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)
What am I doing wrong?
The reason for this is that
time.sleep
is a blocking function: it doesn’t allow control to return to theIOLoop
so that other handlers can be run.Of course,
time.sleep
is often just a placeholder in these examples, the point is to show what happens when something in a handler gets slow. No matter what the real code is doing, to achieve concurrency blocking code must be replaced with non-blocking equivalents. This means one of three things:Find a coroutine-friendly equivalent. For time.sleep, use tornado.gen.sleep instead:
When this option is available, it is usually the best approach. See the Tornado wiki for links to asynchronous libraries that may be useful.
Find a callback-based equivalent. Similar to the first option, callback-based libraries are available for many tasks, although they are slightly more complicated to use than a library designed for coroutines. These are typically used with
tornado.gen.Task
as an adapter:Again, the Tornado wiki can be useful to find suitable libraries.
Run the blocking code on another thread. When asynchronous libraries are not available,
concurrent.futures.ThreadPoolExecutor
can be used to run any blocking code on another thread. This is a universal solution that can be used for any blocking function whether an asynchronous counterpart exists or not:See the Asynchronous I/O chapter of the Tornado user’s guide for more on blocking and asynchronous functions.