forked from openkylin/python-greenlet
181 lines
5.7 KiB
ReStructuredText
181 lines
5.7 KiB
ReStructuredText
.. _what_is_greenlet:
|
|
|
|
===================
|
|
greenlet Concepts
|
|
===================
|
|
|
|
.. currentmodule:: greenlet
|
|
|
|
.. |--| unicode:: U+2013 .. en dash
|
|
.. |---| unicode:: U+2014 .. em dash, trimming surrounding whitespace
|
|
:trim:
|
|
|
|
|
|
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.
|
|
|
|
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, or be :doc:`garbage collected <greenlet_gc>`
|
|
(which raises an exception).
|
|
|
|
.. rubric:: 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("[gr1] main -> test1")
|
|
... gr2.switch()
|
|
... print("[gr1] test1 <- test2")
|
|
... return 'test1 done'
|
|
|
|
>>> def test2():
|
|
... print("[gr2] test1 -> test2")
|
|
... gr1.switch()
|
|
... print("This is never printed.")
|
|
|
|
>>> gr1 = greenlet(test1)
|
|
>>> gr2 = greenlet(test2)
|
|
>>> gr1.switch()
|
|
[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 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:
|
|
|
|
Greenlet Parents
|
|
================
|
|
|
|
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.
|
|
|
|
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
|
|
would kill ``gr2``, and the exception would go back directly into "main".
|
|
The traceback would show ``test2``, but not ``test1``. Remember, switches are not
|
|
calls, but transfer of execution between parallel "stack containers", and
|
|
the "parent" defines which stack logically comes "below" the current one.
|
|
|
|
.. doctest::
|
|
|
|
>>> def test2():
|
|
... print(this_should_be_a_name_error)
|
|
>>> gr1 = greenlet(test1)
|
|
>>> gr2 = greenlet(test2)
|
|
>>> gr1.switch()
|
|
Traceback (most recent call last):
|
|
...
|
|
File "<doctest default[3]>", line 1, in <module>
|
|
gr1.switch()
|
|
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
|