Switching Between Greenlets: Passing Objects and Control¶
Switches between greenlets occur when:
The method
greenlet.switch
of a greenlet is called, in which case execution jumps to the greenlet whoseswitch()
is called; orWhen the method
greenlet.throw
is used to raise an exception in the target greenlet, in which case execution jumps to the greenlet whosethrow
was called; orWhen 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:
>>> 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, ifg
did not start yet, then it will start to run now;args
andkwargs
are passed to the greenlet’srun()
function as its arguments.- Dying greenlet
If a greenlet’s
run()
finishes, its return value is the object sent to its parent. Ifrun()
terminates with an exception, the exception is propagated to its parent (unless it is agreenlet.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 tuple
, and keyword arguments will be a
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:
>>> 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'})
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.)
>>> 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.
>>> 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()
()