summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Madden <jamadden@gmail.com>2020-11-19 10:59:41 -0600
committerJason Madden <jamadden@gmail.com>2020-11-19 10:59:41 -0600
commitdd2517b4ee50c2d08b98203e4c00682be5ae891f (patch)
tree4097048367da0cbaa906be00d02680448e416b9f
parentac501f92a9ef5f3ec80b938647bb3f4919b4e924 (diff)
downloadgreenlet-docs.tar.gz
More restructuring of the docs.docs
-rw-r--r--README.rst30
-rw-r--r--docs/api.rst2
-rw-r--r--docs/creating_executing_greenlets.rst27
-rw-r--r--docs/greenlet.rst407
-rw-r--r--docs/greenlet_gc.rst108
-rw-r--r--docs/gui_example.rst239
-rw-r--r--docs/history.rst5
-rw-r--r--docs/index.rst296
-rw-r--r--docs/python_threads.rst44
-rw-r--r--docs/switching.rst168
10 files changed, 854 insertions, 472 deletions
diff --git a/README.rst b/README.rst
index 2945b19..2d082b1 100644
--- a/README.rst
+++ b/README.rst
@@ -1,27 +1,33 @@
+.. This file is included into docs/history.rst
+
.. image:: https://secure.travis-ci.org/python-greenlet/greenlet.png
:target: http://travis-ci.org/python-greenlet/greenlet
-The greenlet package is a spin-off of Stackless, a version of CPython
-that supports micro-threads called "tasklets". Tasklets run
+Greenlets are lightweight coroutines for in-process concurrent
+programming.
+
+The "greenlet" package is a spin-off of `Stackless`_, a version of
+CPython that supports micro-threads called "tasklets". Tasklets run
pseudo-concurrently (typically in a single or a few OS-level threads)
and are synchronized with data exchanges on "channels".
A "greenlet", on the other hand, is a still more primitive notion of
-micro-thread with no implicit scheduling; coroutines, in other
-words. This is useful when you want to control exactly when your code
-runs. You can build custom scheduled micro-threads on top of greenlet;
+micro-thread with no implicit scheduling; coroutines, in other words.
+This is useful when you want to control exactly when your code runs.
+You can build custom scheduled micro-threads on top of greenlet;
however, it seems that greenlets are useful on their own as a way to
make advanced control flow structures. For example, we can recreate
generators; the difference with Python's own generators is that our
generators can call nested functions and the nested functions can
-yield values too. Additionally, you don't need a "yield" keyword. See
-the example in tests/test_generator.py.
+yield values too. (Additionally, you don't need a "yield" keyword. See
+the example in `test_generator.py
+<https://github.com/python-greenlet/greenlet/blob/adca19bf1f287b3395896a8f41f3f4fd1797fdc7/src/greenlet/tests/test_generator.py#L1>`_).
-Greenlets are provided as a C extension module for the regular
-unmodified interpreter.
+Greenlets are provided as a C extension module for the regular unmodified
+interpreter.
+
+.. _`Stackless`: http://www.stackless.com
-Greenlets are lightweight coroutines for in-process concurrent
-programming.
Who is using Greenlet?
======================
@@ -45,7 +51,7 @@ The easiest way to get Greenlet is to install it with pip::
pip install greenlet
-Source code archives and windows installers are available on the
+Source code archives and binary distributions are vailable on the
python package index at https://pypi.org/project/greenlet
The source code repository is hosted on github:
diff --git a/docs/api.rst b/docs/api.rst
index 82d419a..ab5df55 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -24,7 +24,7 @@ Greenlets
Switches execution to this greenlet. See :ref:`switching`.
- .. automethod:: throw([typ, [val, [tb]]])
+ .. automethod:: throw
.. autoattribute:: dead
diff --git a/docs/creating_executing_greenlets.rst b/docs/creating_executing_greenlets.rst
index 81d0cb6..d10f1ac 100644
--- a/docs/creating_executing_greenlets.rst
+++ b/docs/creating_executing_greenlets.rst
@@ -1,6 +1,9 @@
-==========================================================
- Creating And Executing Greenlets: The Greenlet Lifecycle
-==========================================================
+==================================
+ Creating And Executing Greenlets
+==================================
+
+.. This document is a mess. It's a cross between how-to and API
+ reference.
.. currentmodule:: greenlet
@@ -50,6 +53,11 @@ The ``run`` attribute is deleted at that time.
...
AttributeError: run
+.. _subclassing_greenlet:
+
+Subclassing greenlet
+====================
+
You can also subclass :class:`greenlet.greenlet` and define ``run`` as
a method. This is useful to store additional state with the greenlet.
@@ -71,13 +79,16 @@ a method. This is useful to store additional state with the greenlet.
See :ref:`switching` for more information about switching into greenlets.
+.. _changing_the_parent:
+
Changing The Parent
===================
-When a greenlet finishes, execution resumes with its parent. This
-defaults to the current greenlet when the object was instantiated, but
-can be changed either at that time or any time later. To set it at
-creation time, pass the desired parent as the second argument:
+When a greenlet finishes, :ref:`execution resumes with its parent
+<greenlet_parents>`. This defaults to the current greenlet when the
+object was instantiated, but can be changed either at that time or any
+time later. To set it at creation time, pass the desired parent as the
+second argument:
.. doctest::
@@ -91,7 +102,7 @@ creation time, pass the desired parent as the second argument:
In the child.
In the parent.
-To change it later, assign to the :attr:`greenlet.parent` attribute.
+To change it later, assign to the ``greenlet.parent`` attribute.
.. doctest::
diff --git a/docs/greenlet.rst b/docs/greenlet.rst
index 7325000..c79a23e 100644
--- a/docs/greenlet.rst
+++ b/docs/greenlet.rst
@@ -1,6 +1,8 @@
-=================
- Using greenlets
-=================
+.. _what_is_greenlet:
+
+===================
+ greenlet Concepts
+===================
.. currentmodule:: greenlet
@@ -9,70 +11,151 @@
:trim:
-Introduction
-============
-
A "greenlet" is a small independent pseudo-thread. Think about it as a
small stack of frames; the outermost (bottom) frame is the initial
function you called, and the innermost frame is the one in which the
-greenlet is currently paused. You work with greenlets by creating a
-number of such stacks and jumping execution between them. Jumps are never
-implicit: a greenlet must choose to jump to another greenlet, which will
-cause the former to suspend and the latter to resume where it was
-suspended. Jumping between greenlets is called "switching".
+greenlet is currently paused.
+
+In code, greenlets are represented by objects of class
+:class:`greenlet`. These objects have a few defined attributes, and
+also have a ``__dict__``, allowing for arbitrary user-defined
+attributes.
+
+.. warning::
+
+ Attribute names beginning with ``gr_`` are reserved for this
+ library.
+
+Switching greenlets
+===================
+
+.. seealso:: :doc:`switching`
+
+You work with greenlets by creating a number of such stacks and
+jumping execution between them. Jumps are never implicit: a greenlet
+must choose to jump to another greenlet, which will cause the former
+to suspend and the latter to resume where it was suspended. Jumping
+between greenlets is called "switching". Similarly to
+``generator.send(val)``, switching may pass objects between greenlets.
+
+The greenlet Lifecycle
+======================
+
+.. seealso::
+
+ Details And Examples
+ :doc:`creating_executing_greenlets`
+ Where does execution go when a greenlet dies?
+ :ref:`greenlet_parents`
When you create a greenlet, it gets an initially empty stack; when you
first switch to it, it starts to run a specified function, which may call
other functions, switch out of the greenlet, etc. When eventually the
outermost function finishes its execution, the greenlet's stack becomes
empty again and the greenlet is "dead". Greenlets can also die of an
-uncaught exception.
+uncaught exception, or be :doc:`garbage collected <greenlet_gc>`
+(which raises an exception).
+
+.. rubric:: Example
-For example:
+Let's quickly pull together an example demonstrating those concepts
+before continuing with a few more concepts.
.. doctest::
>>> from greenlet import greenlet
>>> def test1():
- ... print(12)
+ ... print("[gr1] main -> test1")
... gr2.switch()
- ... print(34)
+ ... print("[gr1] test1 <- test2")
... return 'test1 done'
>>> def test2():
- ... print(56)
+ ... print("[gr2] test1 -> test2")
... gr1.switch()
- ... print(78)
+ ... print("This is never printed.")
>>> gr1 = greenlet(test1)
>>> gr2 = greenlet(test2)
>>> gr1.switch()
- 12
- 56
- 34
+ [gr1] main -> test1
+ [gr2] test1 -> test2
+ [gr1] test1 <- test2
'test1 done'
+ >>> gr1.dead
+ True
+ >>> gr2.dead
+ False
+
+
+The line ``gr1.switch()`` jumps to ``test1``, which prints that, jumps
+to ``test2``, and prints that, jumps back into ``test1``, prints that;
+and then ``test1`` finishes and ``gr1`` dies. At this point, the
+execution comes back to the original ``gr1.switch()`` call, which
+returns the value that ``test1`` returned. Note that ``test2`` is
+never switched back to and so doesn't print its final line; it is also
+not dead.
+
+Having seen that, we can continue with a few more concepts.
+
+The Current greenlet
+====================
-The last line jumps to ``test1``, which prints 12, jumps to ``test2``, prints 56,
-jumps back into ``test1``, prints 34; and then ``test1`` finishes and ``gr1`` dies.
-At this point, the execution comes back to the original ``gr1.switch()``
-call, which returns the value that ``test1`` returned. Note that 78 is never printed.
+The greenlet that is actively running code is called the "current
+greenlet." The :class:`greenlet` object representing the current
+greenlet can be obtained by calling :func:`getcurrent`. (Note that
+:ref:`this could be a subclass <subclassing_greenlet>`.)
+
+As long as a greenlet is running, no other greenlet can be running.
+Execution must be explicitly transferred by switching to a different
+greenlet.
+
+The Main greenlet
+=================
+
+Initially, there is one greenlet that you don't have to create: the
+main greenlet. This is the only greenlet that can ever have :ref:`a
+parent of None <greenlet_parents>`. The main greenlet can never be
+dead. This is true for :doc:`every thread in a process
+<python_threads>`.
+
+.. rubric:: Example
+
+.. doctest::
+
+ >>> from greenlet import getcurrent
+ >>> def am_i_main():
+ ... current = getcurrent()
+ ... return current.parent is None
+ >>> am_i_main()
+ True
+ >>> glet = greenlet(am_i_main)
+ >>> glet.switch()
+ False
.. _greenlet_parents:
-Parents
-=======
+Greenlet Parents
+================
-Let's see where execution goes when a greenlet dies. Every greenlet has a
-"parent" greenlet. The parent greenlet is initially the one in which the
-greenlet was created (this can be changed at any time). The parent is
-where execution continues when a greenlet dies. In this way, greenlets are
-organized in a tree. Top-level code that doesn't run in a user-created
-greenlet runs in the implicit "main" greenlet, which is the root of the
-tree.
+Every greenlet, except the main greenlet, has a "parent" greenlet. The
+parent greenlet defaults to being the one in which the greenlet was
+created (this can be :ref:`changed at any time
+<changing_the_parent>`). In this way, greenlets are organized in a
+tree. Top-level code that doesn't run in a user-created greenlet runs
+in the implicit main greenlet, which is the root of the tree.
-In the above example, both ``gr1`` and ``gr2`` have the main greenlet as a parent.
-Whenever one of them dies, the execution comes back to "main".
+The parent is where execution continues when a greenlet dies, whether
+by explicitly returning from its function, "falling off the end" of
+its function, or by raising an uncaught exception.
+
+In the above example, both ``gr1`` and ``gr2`` have the main greenlet
+as a parent. Whenever one of them dies, the execution comes back to
+"main".
+
+Uncaught Exceptions are Raised In the Parent
+--------------------------------------------
Uncaught exceptions are propagated into the parent, too. For example, if
the above ``test2()`` contained a typo, it would generate a :exc:`NameError` that
@@ -95,257 +178,3 @@ the "parent" defines which stack logically comes "below" the current one.
File "<doctest default[0]>", line 2, in test2
print(this_should_be_a_name_error)
NameError: name 'this_should_be_a_name_error' is not defined
-
-Creating Greenlets
-==================
-
-See :doc:`creating_executing_greenlets`.
-
-.. _switching:
-
-Switching
-=========
-
-Switches between greenlets occur when the method ``switch()`` of a greenlet is
-called, in which case execution jumps to the greenlet whose ``switch()`` is
-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::
-
- >>> 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, with the same order of execution as the
-previous example. 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``.
-
-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.)
-
-
-
-Greenlets and Python threads
-============================
-
-Greenlets can be combined with Python threads; in this case, each thread
-contains an independent "main" greenlet with a tree of sub-greenlets. It
-is not possible to mix or switch between greenlets belonging to different
-threads.
-
-.. doctest::
-
- >>> from greenlet import getcurrent
- >>> from threading import Thread
- >>> from threading import Event
- >>> started = Event()
- >>> switched = Event()
- >>> class T(Thread):
- ... def run(self):
- ... self.glet = getcurrent()
- ... started.set()
- ... switched.wait()
- >>> t = T()
- >>> t.start()
- >>> _ = started.wait()
- >>> t.glet.switch()
- Traceback (most recent call last):
- ...
- greenlet.error: cannot switch to a different thread
- >>> switched.set()
- >>> t.join()
-
-Garbage-collecting live greenlets
-=================================
-
-If all the references to a greenlet object go away (including the
-references from the parent attribute of other greenlets), then there
-is no way to ever switch back to this greenlet. In this case, a
-:exc:`GreenletExit` exception is generated into the greenlet. This is
-the only case where a greenlet receives the execution asynchronously
-(without an explicit call to :meth:`greenlet.switch`). This gives
-``try/finally`` blocks a chance to clean up resources held by the
-greenlet. This feature also enables a programming style in which
-greenlets are infinite loops waiting for data and processing it. Such
-loops are automatically interrupted when the last reference to the
-greenlet goes away.
-
-.. doctest::
-
- >>> from greenlet import getcurrent, greenlet, GreenletExit
- >>> def run():
- ... print("Beginning greenlet")
- ... try:
- ... while 1:
- ... print("Switching to parent")
- ... getcurrent().parent.switch()
- ... except GreenletExit:
- ... print("Got GreenletExit; quitting")
-
- >>> glet = greenlet(run)
- >>> _ = glet.switch()
- Beginning greenlet
- Switching to parent
- >>> glet = None
- Got GreenletExit; quitting
-
-The greenlet is expected to either die or be resurrected by having a
-new reference to it stored somewhere; just catching and ignoring the
-`GreenletExit` is likely to lead to an infinite loop.
-
-Greenlets participate in garbage collection in a limited fashion;
-cycles involving data that is present in a greenlet's frames may not
-be detected. Storing references to other greenlets cyclically may lead
-to leaks.
-
-Here, we define a function that creates a cycle; when we run it and
-then collect garbage, the cycle is found and cleared, even while the
-function is running.
-
-.. note:: These examples require Python 3 to run; Python 2 won't
- collect cycles if the ``__del__`` method is defined.
-
-.. doctest::
- :pyversion: >= 3.5
-
- >>> import gc
- >>> class Cycle(object):
- ... def __del__(self):
- ... print("(Running finalizer)")
-
- >>> def collect_it():
- ... print("Collecting garbage")
- ... gc.collect()
- >>> def run(collect=collect_it):
- ... cycle1 = Cycle()
- ... cycle2 = Cycle()
- ... cycle1.cycle = cycle2
- ... cycle2.cycle = cycle1
- ... print("Deleting cycle vars")
- ... del cycle1
- ... del cycle2
- ... collect()
- ... print("Returning")
- >>> run()
- Deleting cycle vars
- Collecting garbage
- (Running finalizer)
- (Running finalizer)
- Returning
-
-If we use the same function in a greenlet, the cycle is also found
-while the greenlet is active:
-
-.. doctest::
- :pyversion: >= 3.5
-
- >>> glet = greenlet(run)
- >>> _ = glet.switch()
- Deleting cycle vars
- Collecting garbage
- (Running finalizer)
- (Running finalizer)
- Returning
-
-If we tweak the function to return control to a different
-greenlet (the main greenlet) and then run garbage collection, the
-cycle is also found:
-
-.. doctest::
- :pyversion: >= 3.5
-
- >>> glet = greenlet(run)
- >>> _ = glet.switch(getcurrent().switch)
- Deleting cycle vars
- >>> collect_it()
- Collecting garbage
- (Running finalizer)
- (Running finalizer)
- >>> del glet
diff --git a/docs/greenlet_gc.rst b/docs/greenlet_gc.rst
new file mode 100644
index 0000000..a83a2de
--- /dev/null
+++ b/docs/greenlet_gc.rst
@@ -0,0 +1,108 @@
+===================================
+ Garbage-collecting Live greenlets
+===================================
+
+If all the references to a greenlet object go away (including the
+references from the parent attribute of other greenlets), then there
+is no way to ever switch back to this greenlet. In this case, a
+:exc:`GreenletExit` exception is generated into the greenlet. This is
+the only case where a greenlet receives the execution asynchronously
+(without an explicit call to :meth:`greenlet.switch`). This gives
+``try/finally`` blocks a chance to clean up resources held by the
+greenlet. This feature also enables a programming style in which
+greenlets are infinite loops waiting for data and processing it. Such
+loops are automatically interrupted when the last reference to the
+greenlet goes away.
+
+.. doctest::
+
+ >>> from greenlet import getcurrent, greenlet, GreenletExit
+ >>> def run():
+ ... print("Beginning greenlet")
+ ... try:
+ ... while 1:
+ ... print("Switching to parent")
+ ... getcurrent().parent.switch()
+ ... except GreenletExit:
+ ... print("Got GreenletExit; quitting")
+
+ >>> glet = greenlet(run)
+ >>> _ = glet.switch()
+ Beginning greenlet
+ Switching to parent
+ >>> glet = None
+ Got GreenletExit; quitting
+
+The greenlet is expected to either die or be resurrected by having a
+new reference to it stored somewhere; just catching and ignoring the
+`GreenletExit` is likely to lead to an infinite loop.
+
+Greenlets participate in garbage collection in a limited fashion;
+cycles involving data that is present in a greenlet's frames may not
+be detected. Storing references to other greenlets cyclically may lead
+to leaks.
+
+Here, we define a function that creates a cycle; when we run it and
+then collect garbage, the cycle is found and cleared, even while the
+function is running.
+
+.. note:: These examples require Python 3 to run; Python 2 won't
+ collect cycles if the ``__del__`` method is defined.
+
+.. doctest::
+ :pyversion: >= 3.5
+
+ >>> import gc
+ >>> class Cycle(object):
+ ... def __del__(self):
+ ... print("(Running finalizer)")
+
+ >>> def collect_it():
+ ... print("Collecting garbage")
+ ... gc.collect()
+ >>> def run(collect=collect_it):
+ ... cycle1 = Cycle()
+ ... cycle2 = Cycle()
+ ... cycle1.cycle = cycle2
+ ... cycle2.cycle = cycle1
+ ... print("Deleting cycle vars")
+ ... del cycle1
+ ... del cycle2
+ ... collect()
+ ... print("Returning")
+ >>> run()
+ Deleting cycle vars
+ Collecting garbage
+ (Running finalizer)
+ (Running finalizer)
+ Returning
+
+If we use the same function in a greenlet, the cycle is also found
+while the greenlet is active:
+
+.. doctest::
+ :pyversion: >= 3.5
+
+ >>> glet = greenlet(run)
+ >>> _ = glet.switch()
+ Deleting cycle vars
+ Collecting garbage
+ (Running finalizer)
+ (Running finalizer)
+ Returning
+
+If we tweak the function to return control to a different
+greenlet (the main greenlet) and then run garbage collection, the
+cycle is also found:
+
+.. doctest::
+ :pyversion: >= 3.5
+
+ >>> glet = greenlet(run)
+ >>> _ = glet.switch(getcurrent().switch)
+ Deleting cycle vars
+ >>> collect_it()
+ Collecting garbage
+ (Running finalizer)
+ (Running finalizer)
+ >>> del glet
diff --git a/docs/gui_example.rst b/docs/gui_example.rst
new file mode 100644
index 0000000..047ed10
--- /dev/null
+++ b/docs/gui_example.rst
@@ -0,0 +1,239 @@
+
+.. _gui_example:
+
+==================================================================
+ Motivation: Treating an Asynchronous GUI Like a Synchronous Loop
+==================================================================
+
+.. currentmodule:: greenlet
+
+In this document, we'll demonstrate how greenlet can be used to
+connect synchronous and asynchronous operations, without introducing
+any additional threads or race conditions. We'll use the example of
+transforming a "pull"-based console application into an asynchronous
+"push"-based GUI application *while still maintaining the simple
+pull-based structure*.
+
+Similar techniques work with XML expat parsers; in general, it can be
+framework that issues asynchronous callbacks.
+
+.. |--| unicode:: U+2013 .. en dash
+.. |---| unicode:: U+2014 .. em dash, trimming surrounding whitespace
+ :trim:
+
+
+A Simple Terminal App
+=====================
+
+Let's consider a system controlled by a terminal-like console, where
+the user types commands. Assume that the input comes character by
+character. In such a system, there will typically be a loop like the
+following one:
+
+.. doctest::
+
+ >>> def echo_user_input(user_input):
+ ... print(' <<< ' + user_input.strip())
+ ... return user_input
+ >>> def process_commands():
+ ... while True:
+ ... line = ''
+ ... while not line.endswith('\n'):
+ ... line += read_next_char()
+ ... echo_user_input(line)
+ ... if line == 'quit\n':
+ ... print("Are you sure?")
+ ... if echo_user_input(read_next_char()) != 'y':
+ ... continue # ignore the command
+ ... print("(Exiting loop.)")
+ ... break # stop the command loop
+ ... process_command(line)
+
+Here, we have an infinite loop. The job of the loop is to read characters
+that the user types, accumulate that into a command line, and then
+execute the command. The heart of the loop is around ``read_next_char()``:
+
+.. doctest::
+
+ >>> def read_next_char():
+ ... """
+ ... Called from `process_commands`;
+ ... blocks until a character has been typed and returns it.
+ ... """
+
+This function might be implemented by simply reading from
+:obj:`sys.stdin`, or by something more complex such as
+:meth:`curses.window.getch`, but in any case, it doesn't return until
+a key has been read from the user.
+
+Competing Event Loops
+=====================
+
+Now assume that you want to plug this program into a GUI. Most GUI
+toolkits are event-based. Internally, they run their own infinite loop
+much like the one we wrote above, invoking a call-back for each
+character the user presses (``event_keydown(key)``).
+
+.. doctest::
+
+ >>> def event_keydown(key):
+ ... "Called by the event system *asynchronously*."
+
+
+In this setting, it is difficult to implement the ``read_next_char()``
+function needed by the code above. We have two incompatible functions.
+First, there's the function the GUI will call asynchronously to notify
+about an event; it's important to stress that we're not in control of
+when this function is called |---| in fact, our code isn't in the call
+stack at all, the GUI's loop is the only thing running. But that
+doesn't fit with our second function, ``read_next_char()`` which itself
+is supposed to be blocking and called from the middle of its own loop.
+
+How can we fit this asynchronous delivery mechanism together with our
+synchronous, blocking function that reads the next character the user
+types?
+
+Enter greenlets: Dual Infinite Loops
+====================================
+
+You might consider doing that with :class:`threads <threading.Thread>`
+[#f1]_, but that can get complicated rather quickly. greenlets are an
+alternate solution that don't have the related locking and other
+problems threads introduce.
+
+By introducing a greenlet to run ``process_commands``, and having it
+communicate with the greenlet running the GUI event loop, we can
+effectively have a single thread be *in the middle of two infinite
+loops at once* and switch between them as desired. Pretty cool.
+
+It's even cooler when you consider that the GUI's loop is likely to be
+implemented in C, not Python, so we'll be switching between infinite
+loops both in native code and in the Python interpreter.
+
+First, let's create a greenlet to run the ``process_commands`` function
+(note that we're not starting it just yet, only defining it).
+
+.. doctest::
+
+ >>> from greenlet import greenlet
+ >>> g_processor = greenlet(process_commands)
+
+Now, we need to arrange for the communication between the GUI's event
+loop and its callback ``event_keydown`` (running in the implicit main
+greenlet) and this new greenlet. The changes to ``event_keydown`` are
+pretty simple: just send the key the GUI gives us into the loop that
+``process_commands`` is in using :meth:`greenlet.switch`.
+
+.. doctest::
+
+ >>> main_greenlet = greenlet.getcurrent()
+ >>> def event_keydown(key): # running in main_greenlet
+ ... # jump into g_processor, sending it the key
+ ... g_processor.switch(key)
+
+The other side of the coin is to define ``read_next_char`` to accept
+this key event. We do this by letting the main greenlet run the GUI
+loop until the GUI loop jumps back to is from ``event_keydown``:
+
+.. doctest::
+
+ >>> def read_next_char(): # running in g_processor
+ ... # jump to the main greenlet, where the GUI event
+ ... # loop is running, and wait for the next key
+ ... next_char = main_greenlet.switch('blocking in read_next_char')
+ ... return next_char
+
+Having defined both functions, we can start the ``process_commands``
+greenlet, which will make it to ``read_next_char()`` and immediately
+switch back to the main greenlet:
+
+.. doctest::
+
+ >>> g_processor.switch()
+ 'blocking in read_next_char'
+
+Now we can hand control over to the main event loop of the GUI. Of
+course, in documentation we don't have a GUI, so we'll fake one that
+feeds keys to ``event_keydown``; for demonstration purposes we'll also
+fake a ``process_command`` function that just prints the line it got.
+
+.. doctest::
+
+ >>> def process_command(line):
+ ... print('(Processing command: ' + line.strip() + ')')
+
+ >>> def gui_mainloop():
+ ... # The user types "hello"
+ ... for c in 'hello\n':
+ ... event_keydown(c)
+ ... # The user types "quit"
+ ... for c in 'quit\n':
+ ... event_keydown(c)
+ ... # The user responds to the prompt with 'y'
+ ... event_keydown('y')
+
+ >>> gui_mainloop()
+ <<< hello
+ (Processing command: hello)
+ <<< quit
+ Are you sure?
+ <<< y
+ (Exiting loop.)
+ >>> g_processor.dead
+ True
+
+.. sidebar:: Switching Isn't Contagious
+
+ Notice how a single call to ``gui_mainloop`` successfully switched
+ back and forth between two greenlets without the caller or author of
+ ``gui_mainloop`` needing to be aware of that.
+
+ Contrast this with :mod:`asyncio`, where the keywords ``async def`` and
+ ``await`` often spread throughout the codebase once introduced.
+
+ In fact, greenlets can be used to put a halt to that spread and
+ execute ``async def`` code in a synchronous fashion.
+
+ .. seealso::
+
+ For the interactions between :mod:`contextvars` and greenlets.
+ :doc:`contextvars`
+
+In this example, the execution flow is: when ``read_next_char()`` is called, it
+is part of the ``g_processor`` greenlet, so when it switches to its parent
+greenlet, it resumes execution in the top-level main loop (the GUI). When
+the GUI calls ``event_keydown()``, it switches to ``g_processor``, which means that
+the execution jumps back wherever it was suspended in that greenlet |---| in
+this case, to the ``switch()`` instruction in ``read_next_char()`` |---| and the ``key``
+argument in ``event_keydown()`` is passed as the return value of the switch() in
+``read_next_char()``.
+
+Note that ``read_next_char()`` will be suspended and resumed with its call stack
+preserved, so that it will itself return to different positions in
+``process_commands()`` depending on where it was originally called from. This
+allows the logic of the program to be kept in a nice control-flow way; we
+don't have to completely rewrite ``process_commands()`` to turn it into a state
+machine.
+
+Further Reading
+===============
+
+Continue reading with :doc:`greenlet`.
+
+Curious how execution resumed in the main greenlet after
+``process_commands`` exited its loop (and never explicitly switched
+back to the main greenlet)? Read about :ref:`greenlet_parents`.
+
+.. rubric:: Footnotes
+
+.. [#f1] You might try to run the GUI event loop in one thread, and
+ the ``process_commands`` function in another thread. You
+ could then use a thread-safe :class:`queue.Queue` to
+ exchange keypresses between the two: write to the queue in
+ ``event_keydown``, read from it in ``read_next_char``. One
+ problem with this, though, is that many GUI toolkits are
+ single-threaded and only run in the main thread, so we'd
+ also need a way to communicate any results of
+ ``process_command`` back to the main thread in order to
+ update the GUI. We're now significantly diverging from our
+ simple console-based application.
diff --git a/docs/history.rst b/docs/history.rst
new file mode 100644
index 0000000..f287d56
--- /dev/null
+++ b/docs/history.rst
@@ -0,0 +1,5 @@
+===================
+ History And About
+===================
+
+.. include:: ../README.rst
diff --git a/docs/index.rst b/docs/index.rst
index e14be15..52ed4f1 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -2,182 +2,154 @@
greenlet: Lightweight concurrent programming
==============================================
-.. TODO: Refactor and share the opening paragraphs with README.rst
-.. TODO: Break into a few pieces: Introduction, tutorial, API
- reference, etc. https://documentation.divio.com/explanation/
+..
+ TODO: Divide into a few different kinds of documentation
+ (https://documentation.divio.com/explanation/):
-Contents
-========
-
-.. toctree::
- :maxdepth: 1
-
- greenlet
- creating_executing_greenlets
- contextvars
- tracing
- api
- c_api
- changes
- development
+ - Tutorial,
+ - API reference
+ - how-to.
+ - Explanation.
+ Each document should identify what role it fulfills.
.. |--| unicode:: U+2013 .. en dash
.. |---| unicode:: U+2014 .. em dash, trimming surrounding whitespace
:trim:
+.. sidebar:: Contents
-Motivation
-==========
-
-The "greenlet" package is a spin-off of `Stackless`_, a version of CPython
-that supports micro-threads called "tasklets". Tasklets run
-pseudo-concurrently (typically in a single or a few OS-level threads) and
-are synchronized with data exchanges on "channels".
-
-A "greenlet", on the other hand, is a still more primitive notion of
-micro-thread with no implicit scheduling; coroutines, in other words.
-This is useful when you want to
-control exactly when your code runs. You can build custom scheduled
-micro-threads on top of greenlet; however, it seems that greenlets are
-useful on their own as a way to make advanced control flow structures.
-For example, we can recreate generators; the difference with Python's own
-generators is that our generators can call nested functions and the nested
-functions can yield values too. (Additionally, you don't need a "yield"
-keyword. See the example in ``test/test_generator.py``).
-
-Greenlets are provided as a C extension module for the regular unmodified
-interpreter.
-
-.. _`Stackless`: http://www.stackless.com
-
-Example
--------
-
-Let's consider a system controlled by a terminal-like console, where the user
-types commands. Assume that the input comes character by character. In such
-a system, there will typically be a loop like the following one:
-
-.. doctest::
-
- >>> def echo_user_input(user_input):
- ... print(' <<< ' + user_input.strip())
- ... return user_input
- >>> def process_commands():
- ... while True:
- ... line = ''
- ... while not line.endswith('\n'):
- ... line += read_next_char()
- ... echo_user_input(line)
- ... if line == 'quit\n':
- ... print("Are you sure?")
- ... if echo_user_input(read_next_char()) != 'y':
- ... continue # ignore the command
- ... print("(Exiting loop.)")
- ... break # stop the command loop
- ... process_command(line)
-
-Now assume that you want to plug this program into a GUI. Most GUI
-toolkits are event-based. They will invoke a call-back for each
-character the user presses (``event_keydown(key)``) [#f1]_. In this setting,
-it is difficult to implement the ``read_next_char()`` function needed
-by the code above. We have two incompatible functions:
-
-.. doctest::
-
- >>> def event_keydown(key):
- ... "Called by the event system asynchronously."
-
- >>> def read_next_char():
- ... """
- ... Called from `process_commands`; should wait for
- ... the next `event_keydown()` call.
- ... """
-
-You might consider doing that with threads. Greenlets are an alternate
-solution that don't have the related locking and shutdown problems. You
-start the ``process_commands()`` function in its own, separate greenlet, and
-then you exchange the keypresses with it as follows:
-
-.. doctest::
-
- >>> from greenlet import greenlet
- >>> g_processor = greenlet(process_commands)
- >>> def event_keydown(key):
- ... # jump into g_processor, sending it the key
- ... g_processor.switch(key)
-
- >>> def read_next_char():
- ... # g_self is g_processor in this simple example
- ... g_self = greenlet.getcurrent()
- ... assert g_self is g_processor
- ... # jump to the parent (main) greenlet, where the GUI event
- ... # loop is running, and wait for the next key
- ... main_greenlet = g_self.parent
- ... next_char = main_greenlet.switch()
- ... return next_char
-
-Next, we can start the processor, which will immediately switch back
-to the main greenlet:
-
-.. doctest::
-
- >>> _ = g_processor.switch()
-
-Now we can hand control over to the main event loop of the GUI. Of
-course, in documentation we don't have a gui, so we'll fake one that
-feeds keys to ``event_keydown``; we'll also fake a ``process_command``
-function that just prints the line it got.
-
-.. doctest::
-
- >>> def process_command(line):
- ... print('(Processing command: ' + line.strip() + ')')
-
- >>> def gui_mainloop():
- ... # The user types "hello"
- ... for c in 'hello\n':
- ... event_keydown(c)
- ... # The user types "quit"
- ... for c in 'quit\n':
- ... event_keydown(c)
- ... # The user responds to the prompt with 'y'
- ... event_keydown('y')
-
- >>> gui_mainloop()
- <<< hello
- (Processing command: hello)
- <<< quit
- Are you sure?
- <<< y
- (Exiting loop.)
-
-In this example, the execution flow is: when ``read_next_char()`` is called, it
-is part of the ``g_processor`` greenlet, so when it switches to its parent
-greenlet, it resumes execution in the top-level main loop (the GUI). When
-the GUI calls ``event_keydown()``, it switches to ``g_processor``, which means that
-the execution jumps back wherever it was suspended in that greenlet |---| in
-this case, to the ``switch()`` instruction in ``read_next_char()`` |---| and the ``key``
-argument in ``event_keydown()`` is passed as the return value of the switch() in
-``read_next_char()``.
-
-Note that ``read_next_char()`` will be suspended and resumed with its call stack
-preserved, so that it will itself return to different positions in
-``process_commands()`` depending on where it was originally called from. This
-allows the logic of the program to be kept in a nice control-flow way; we
-don't have to completely rewrite ``process_commands()`` to turn it into a state
-machine.
-
-Continue reading with :doc:`greenlet`.
+ If this page has piqued your interest in greenlets,
+ continue reading by seeing :ref:`an example transforming an
+ asynchronous GUI into a simple synchronous loop <gui_example>`.
+
+ To get started building your own code with greenlets, read
+ :doc:`greenlet`, and then :doc:`creating_executing_greenlets`.
+ .. toctree::
+ :caption: Getting Started
+ :maxdepth: 2
-.. rubric:: Footnotes
+ gui_example
+ greenlet
+ creating_executing_greenlets
+ switching
+
+ .. toctree::
+ :maxdepth: 1
+ :caption: Reference Material
+
+ api
+ c_api
+ changes
+ development
+ history
+
+ .. toctree::
+ :maxdepth: 1
+ :caption: Advanced Usage
+
+ python_threads
+ contextvars
+ greenlet_gc
+ tracing
+
+.. rubric:: What are greenlets?
+
+greenlets are lightweight coroutines for in-process sequential concurrent
+programming.
+
+greenlets can be used on their own, but they are frequently used with
+frameworks such as `gevent`_ to provide higher-level abstractions and
+asynchronous I/O.
+
+greenlets are frequently defined by analogy to :mod:`threads
+<threading>` or Python's built-in coroutines (generators and ``async
+def`` functions). The rest of this section will explore those
+analogies. For a more precise introduction, see :ref:`what_is_greenlet`.
+
+See :doc:`history` for how the greenlet library was created, and its
+relation to other similar concepts.
+
+.. rubric:: Are greenlets similar to threads?
+
+For many purposes, you can usually think of greenlets as cooperatively
+scheduled :mod:`threads <threading>`. The major differences are
+that since they're cooperatively scheduled, you are in control of
+when they execute, and since they are coroutines, many greenlets can
+exist in a single native thread.
-.. [#f1] Replace "GUI" with "XML expat parser" if that rings more bells to
- you. In general, it can be framework that issues asynchronous callbacks.
+.. rubric:: How are greenlets different from threads?
+
+Threads (in theory) are preemptive and parallel [#f1]_, meaning that multiple
+threads can be processing work at the same time, and it's impossible
+to say in what order different threads will proceed or see the effects
+of other threads. This necessitates careful programming using
+:class:`locks <threading.Lock>`, :class:`queues <queue.Queue>`, or
+other approaches to avoid `race conditions`_, `deadlocks`_, or other
+bugs.
+
+In contrast, greenlets are cooperative and sequential. This means that
+when one greenlet is running, no other greenlet can be running; the
+programmer is fully in control of when execution switches between
+greenlets. This can eliminate race conditions and greatly simplify the
+programming task.
+
+Also, threads require resources from the operating system (the thread
+stack, and bookeeping in the kernel). Because greenlets are
+implemented entirely without involving the operating system, they can
+require fewer resources; it is often practical to have many more
+greenlets than it is threads.
+
+.. _race conditions: https://en.wikipedia.org/wiki/Race_condition
+.. _deadlocks: https://docs.microsoft.com/en-us/troubleshoot/dotnet/visual-basic/race-conditions-deadlocks#when-deadlocks-occur
+
+.. rubric:: How else can greenlets be used?
+
+greenlets have many uses:
+
+- They can be treated like cooperative threads. You can implement any
+ scheduling policy you desire.
+- Because greenlets work well with C libraries (greenlets can switch
+ even with C functions in the call stack), they are well suited for
+ integrating with GUIs or other event loops.
+
+ `gevent`_ is an example of using greenlets to integrate with IO
+ event loops (`libev`_ and `libuv`_) to provide a complete
+ asynchronous environment using familiar programming patterns.
+- Similar to the above, greenlets can be used to transform apparently
+ asynchronous tasks into a simple synchronous style. See
+ :ref:`gui_example` for an example of writing an asynchronous event-driven GUI app
+ in a simple synchronous style.
+- In general, greenlets can be used for advanced control flow. For
+ example, you can :doc:`create generators <history>` |---| without
+ the use of the ``yield`` keyword!
+
+
+.. _gevent: https://www.gevent.org
+.. _libev: http://software.schmorp.de/pkg/libev.html
+.. _libuv: http://libuv.org/
+
+.. rubric:: Are greenlets similar to generators? What about asyncio?
+
+All three of greenlets, generators, and asyncio use a concept of
+coroutines. However, greenlets, unlike the other two, require no
+special keywords or support from the Python language. In addition,
+greenlets are capable of switching between stacks that feature C
+libraries, whereas the other two are not.
+
+
+.. rubric:: Footnotes
+.. [#f1] In CPython, the `global interpreter lock (GIL)
+ <https://wiki.python.org/moin/GlobalInterpreterLock>`_
+ generally prevents two threads from executing Python code at
+ the same time. Parallelism is thus limited to code sections
+ that release the GIL, i.e., C code.
Indices and tables
==================
* :ref:`search`
+* :ref:`genindex`
+* :ref:`modindex`
diff --git a/docs/python_threads.rst b/docs/python_threads.rst
new file mode 100644
index 0000000..7389d3d
--- /dev/null
+++ b/docs/python_threads.rst
@@ -0,0 +1,44 @@
+==============================
+ Greenlets and Python Threads
+==============================
+
+Greenlets can be combined with Python threads; in this case, each thread
+contains an independent "main" greenlet with a tree of sub-greenlets. It
+is not possible to mix or switch between greenlets belonging to different
+threads.
+
+.. doctest::
+
+ >>> from greenlet import getcurrent
+ >>> from threading import Thread
+ >>> from threading import Event
+ >>> started = Event()
+ >>> switched = Event()
+ >>> class T(Thread):
+ ... def run(self):
+ ... self.glet = getcurrent()
+ ... started.set()
+ ... switched.wait()
+ >>> t = T()
+ >>> t.start()
+ >>> _ = started.wait()
+ >>> t.glet.switch()
+ Traceback (most recent call last):
+ ...
+ greenlet.error: cannot switch to a different thread
+ >>> switched.set()
+ >>> t.join()
+
+Note that when a thread dies, the thread's main greenlet is not
+considered to be dead.
+
+.. doctest::
+
+ >>> t.glet.dead
+ False
+
+.. caution::
+
+ For these reasons, it's best to not pass references to a greenlet
+ running in one thread to another thread. If you do, take caution to
+ carefully manage the lifetime of the references.
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()
+ ()