Search code examples
pythonmultithreadingasynchronoustornado

Tornado: how to launch asynchronous threads


I'm following the Tornado tutorial here: http://www.tornadoweb.org/en/stable/locks.html#tornado.locks.Condition

Now instead of one waiter, I want two threads waiting for the same condition variable. When the condition is released, I want the threads to start doing work asynchronously.

Here's my attempt to do this:

from time import sleep
from tornado import gen
from tornado.ioloop import IOLoop
from tornado.locks import Condition

condition = Condition()

@gen.coroutine
def waiter():
    print('1: waiting')
    yield condition.wait()  # Yield a Future.
    print('1: finish waiting')
    for i in range(5):
        sleep(1)
        print('1: doing work', i)

@gen.coroutine
def waiter2():
    print('2: waiting')
    yield condition.wait()  # Yield a Future.
    print('2: finish waiting')
    for i in range(5):
        sleep(1)
        print('2: doing work', i)

@gen.coroutine
def notifier():
    print("About to notify")
    sleep(2)
    condition.notify_all()
    print("Done notifying")

@gen.coroutine
def runner():
    # combines the futures
    yield [waiter(), waiter2(), notifier()]

IOLoop.current().run_sync(runner)

However, waiter2 always wakes up and starts doing work after waiter1 exits.

I have also attempted to call IOLoop.current().run_sync() twice, but Tornado throws RuntimeError: IOLoop has already started.

Could anyone please show me what's the correct code to launch threads asynchronously? Thanks!

EDIT

The answer points out that sleep should be replaced by gen.sleep. This is completely correct for the code snippet I posted, so thank you for that.

However, the sleep() here is only for illustration purpose. What I really want is waiter() and waiter2() doing long-running blocking processing simultaneously. How can I achieve that?


Solution

  • You cannot call time.sleep in your code because such a long blocking call interferes fundamentally with the way tornado's IOLoop works. Replace your calls to time.sleep with the tornado.gen.sleep coroutine and your code works just fine:

    from time import sleep
    from tornado import gen
    from tornado.ioloop import IOLoop
    from tornado.locks import Condition
    
    condition = Condition()
    
    @gen.coroutine
    def waiter():
        print('1: waiting')
        yield condition.wait()  # Yield a Future.
        print('1: finish waiting')
        for i in range(5):
            yield gen.sleep(1)
            print('1: doing work', i)
    
    @gen.coroutine
    def waiter2():
        print('2: waiting')
        yield condition.wait()  # Yield a Future.
        print('2: finish waiting')
        for i in range(5):
            yield gen.sleep(1)
            print('2: doing work', i)
    
    @gen.coroutine
    def notifier():
        print("About to notify")
        yield gen.sleep(2)
        condition.notify_all()
        print("Done notifying")
    
    @gen.coroutine
    def runner():
        # combines the futures
        yield [waiter(), waiter2(), notifier()]
    
    IOLoop.current().run_sync(runner)