Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
635 views
in Technique[技术] by (71.8m points)

asynchronous - Why doesn't time.sleep run in parallel in a Tornado coroutine?

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The reason for this is that time.sleep is a blocking function: it doesn’t allow control to return to the IOLoop 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:

    class CoroutineSleepHandler(RequestHandler):
        @gen.coroutine
        def get(self):
            for i in range(5):
                print(i)
                yield gen.sleep(1)
    

    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:

    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)
    

    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:

    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)
    

    See the Asynchronous I/O chapter of the Tornado user’s guide for more on blocking and asynchronous functions.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

56.9k users

...