diff options
Diffstat (limited to 'docs/switching.rst')
| -rw-r--r-- | docs/switching.rst | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/docs/switching.rst b/docs/switching.rst new file mode 100644 index 0000000..b1de9b8 --- /dev/null +++ b/docs/switching.rst @@ -0,0 +1,168 @@ +.. _switching: + +========================================================== + Switching Between Greenlets: Passing Objects and Control +========================================================== + +.. This is an "explanation" document. + +.. currentmodule:: greenlet + +Switches between greenlets occur when: + +- The method `greenlet.switch` of a greenlet is + called, in which case execution jumps to the greenlet whose ``switch()`` is + called; or +- When the method `greenlet.throw` is used to raise an exception in + the target greenlet, in which case execution jumps to the greenlet + whose ``throw`` was called; or +- When a greenlet dies, in which case execution jumps to the + parent greenlet. + +During a switch, an object or an exception is "sent" to the target +greenlet; this can be used as a convenient way to pass information +between greenlets. For example: + +.. doctest:: + + >>> from greenlet import greenlet + >>> def test1(x, y): + ... z = gr2.switch(x + y) + ... print(z) + + >>> def test2(u): + ... print(u) + ... gr1.switch(42) + + >>> gr1 = greenlet(test1) + >>> gr2 = greenlet(test2) + >>> gr1.switch("hello", " world") + hello world + 42 + +This prints "hello world" and 42. Note that the arguments of +``test1()`` and ``test2()`` are not provided when the greenlet is +created, but only the first time someone switches to it. + +Here are the precise rules for sending objects around: + +``g.switch(*args, **kwargs)`` + Switches execution to the greenlet ``g``, sending it the given + arguments. As a special case, if ``g`` did not start yet, then it + will start to run now; ``args`` and ``kwargs`` are passed to the + greenlet's ``run()`` function as its arguments. + +Dying greenlet + If a greenlet's ``run()`` finishes, its return value is the object + sent to its parent. If ``run()`` terminates with an exception, the + exception is propagated to its parent (unless it is a + ``greenlet.GreenletExit`` exception, in which case the exception + object is caught and *returned* to the parent). + +Apart from the cases described above, the target greenlet normally +receives the object as the return value of the call to ``switch()`` in +which it was previously suspended. Indeed, although a call to +``switch()`` does not return immediately, it will still return at some +point in the future, when some other greenlet switches back. When this +occurs, then execution resumes just after the ``switch()`` where it was +suspended, and the ``switch()`` itself appears to return the object that +was just sent. This means that ``x = g.switch(y)`` will send the object +``y`` to ``g``, and will later put the (unrelated) object that some +(unrelated) greenlet passes back to us into ``x``. + +Multiple And Keyword Arguments +============================== + +You can pass multiple or keyword arguments to ``switch()``. If the +greenlet hasn't begun running, those are passed as function arguments +to ``run`` as usual in Python. If the greenlet *was* running, multiple +arguments will be a :class:`tuple`, and keyword arguments will be a +:class:`dict`; any number of positional arguments with keyword +arguments will have the entire set in a tuple, with positional +arguments in their own nested tuple, and keyword arguments as a `dict` +in the the last element of the tuple: + +.. doctest:: + + >>> def test1(x, y, **kwargs): + ... while 1: + ... z = gr2.switch(x + y + ' ' + str(kwargs)) + ... if not z: break + ... print(z) + + >>> def test2(u): + ... print(u) + ... # A single argument -> itself + ... gr1.switch(42) + ... # Multiple positional args -> a tuple + ... gr1.switch("how", "are", "you") + ... # Only keyword arguments -> a dict + ... gr1.switch(language='en') + ... # one positional and keywords -> ((tuple,), dict) + ... gr1.switch("howdy", language='en_US') + ... # multiple positional and keywords -> ((tuple,), dict) + ... gr1.switch("all", "y'all", language='en_US_OK') + ... gr1.switch(None) # terminate + + >>> gr1 = greenlet(test1) + >>> gr2 = greenlet(test2) + >>> gr1.switch("hello", " world", language='en') + hello world {'language': 'en'} + 42 + ('how', 'are', 'you') + {'language': 'en'} + (('howdy',), {'language': 'en_US'}) + (('all', "y'all"), {'language': 'en_US_OK'}) + +.. _switch_to_dead: + +Switching To Dead Greenlets +=========================== + +Note that any attempt to switch to a dead greenlet actually goes to the +dead greenlet's parent, or its parent's parent, and so on. (The final +parent is the "main" greenlet, which is never dead.) + + +.. doctest:: + + >>> def inner(): + ... print("Entering inner.") + ... print("Returning from inner.") + ... return 42 + >>> def outer(): + ... print("Entering outer and spawning inner.") + ... inner_glet = greenlet(inner) + ... print("Switching to inner.") + ... result = inner_glet.switch() + ... print("Got from inner value: %s" % (result,)) + ... print("Switching to inner again.") + ... result = inner_glet.switch() + ... print("Got from inner value: %s" % (result,)) + ... return inner_glet + >>> outer_glet = greenlet(outer) + +Here, our main greenlet has created another greenlet (``outer``), which in turn +creates a greenlet (``inner``). The outer greenlet switches to the +inner greenlet, which immediately finishes and dies; the outer greenlet +attempts to switch back to the inner greenlet, but since the inner +greenlet is dead, it just switches...to itself (since it was the +parent). Note how the second switch (to the dead greenlet) returns an +empty tuple. + +.. doctest:: + + >>> inner_glet = outer_glet.switch() + Entering outer and spawning inner. + Switching to inner. + Entering inner. + Returning from inner. + Got from inner value: 42 + Switching to inner again. + Got from inner value: () + +We can similarly ask the main greenlet to switch to the (dead) inner +greenlet and its (dead) parent and wind up still in the main greenlet. + + >>> inner_glet.switch() + () |
