forked from openkylin/python-greenlet
169 lines
6.0 KiB
ReStructuredText
169 lines
6.0 KiB
ReStructuredText
.. _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()
|
|
()
|