Frequently Asked Questions¶
What’s it for?¶
Toro makes it easy for Tornado coroutines–that is, functions decorated with gen.coroutine–to coordinate using Events, Conditions, Queues, and Semaphores. Toro supports patterns in which coroutines wait for notifications from others.
Why the name?¶
A coroutine is often called a “coro”, and a library of primitives useful for managing coroutines is called “coros” in Gevent and “coro” in Shrapnel. So I call a library to manage Tornado coroutines “toro”.
Why do I need synchronization primitives for a single-threaded app?¶
Protecting an object shared across coroutines is mostly unnecessary in a
single-threading Tornado program. For example, a multithreaded app would protect
counter
with a Lock:
import threading
lock = threading.Lock()
counter = 0
def inc():
lock.acquire()
counter += 1
lock.release()
This isn’t needed in a Tornado coroutine, because the coroutine won’t be interrupted until it explicitly yields. Thus Toro is not designed to protect shared state.
Instead, Toro supports complex coordination among coroutines with The Wait / Notify Pattern: Some coroutines wait at particular points in their code for other coroutines to awaken them.
Why no RLock?¶
The standard-library RLock (reentrant lock) can be acquired multiple times by a single thread without blocking, reducing the chance of deadlock, especially in recursive functions. The thread currently holding the RLock is the “owning thread.”
In Toro, simulating a concept like an “owning chain of coroutines” would be
over-complicated and under-useful, so there is no RLock, only a Lock
.
Has Toro anything to do with Tulip?¶
Toro predates Tulip, which has very similar ideas about coordinating async coroutines using locks and queues. Toro’s author implemented Tulip’s queues, and version 0.5 of Toro strives to match Tulip’s API.
The chief differences between Toro and Tulip are that Toro uses yield
instead of yield from
, and that Toro uses absolute deadlines instead of
relative timeouts. Additionally, Toro’s Lock
and
Semaphore
aren’t context managers (they can’t be used with a
with
statement); instead, the Futures returned from
Lock.acquire()
and Semaphore.acquire()
are context
managers:
>>> from tornado import gen
>>> import toro
>>> lock = toro.Lock()
>>>
>>> @gen.coroutine
... def f():
... with (yield lock.acquire()):
... assert lock.locked()
...
... assert not lock.locked()