1
# Coroutine implementation using Python threads.
3
# Combines ideas from Guido's Generator module, and from the coroutine
4
# features of Icon and Simula 67.
6
# To run a collection of functions as coroutines, you need to create
7
# a Coroutine object to control them:
9
# and then 'create' a subsidiary object for each function in the
11
# cof1 = co.create(f1 [, arg1, arg2, ...]) # [] means optional,
12
# cof2 = co.create(f2 [, arg1, arg2, ...]) #... not list
13
# cof3 = co.create(f3 [, arg1, arg2, ...])
14
# etc. The functions need not be distinct; 'create'ing the same
15
# function multiple times gives you independent instances of the
18
# To start the coroutines running, use co.tran on one of the create'd
19
# functions; e.g., co.tran(cof2). The routine that first executes
20
# co.tran is called the "main coroutine". It's special in several
21
# respects: it existed before you created the Coroutine object; if any of
22
# the create'd coroutines exits (does a return, or suffers an unhandled
23
# exception), EarlyExit error is raised in the main coroutine; and the
24
# co.detach() method transfers control directly to the main coroutine
25
# (you can't use co.tran() for this because the main coroutine doesn't
28
# Coroutine objects support these methods:
30
# handle = .create(func [, arg1, arg2, ...])
31
# Creates a coroutine for an invocation of func(arg1, arg2, ...),
32
# and returns a handle ("name") for the coroutine so created. The
33
# handle can be used as the target in a subsequent .tran().
35
# .tran(target, data=None)
36
# Transfer control to the create'd coroutine "target", optionally
37
# passing it an arbitrary piece of data. To the coroutine A that does
38
# the .tran, .tran acts like an ordinary function call: another
39
# coroutine B can .tran back to it later, and if it does A's .tran
40
# returns the 'data' argument passed to B's tran. E.g.,
42
# in coroutine coA in coroutine coC in coroutine coB
43
# x = co.tran(coC) co.tran(coB) co.tran(coA,12)
46
# The data-passing feature is taken from Icon, and greatly cuts
47
# the need to use global variables for inter-coroutine communication.
50
# The same as .tran(invoker, data=None), where 'invoker' is the
51
# coroutine that most recently .tran'ed control to the coroutine
52
# doing the .back. This is akin to Icon's "&source".
54
# .detach( data=None )
55
# The same as .tran(main, data=None), where 'main' is the
56
# (unnameable!) coroutine that started it all. 'main' has all the
57
# rights of any other coroutine: upon receiving control, it can
58
# .tran to an arbitrary coroutine of its choosing, go .back to
59
# the .detach'er, or .kill the whole thing.
62
# Destroy all the coroutines, and return control to the main
63
# coroutine. None of the create'ed coroutines can be resumed after a
64
# .kill(). An EarlyExit exception does a .kill() automatically. It's
65
# a good idea to .kill() coroutines you're done with, since the
66
# current implementation consumes a thread for each coroutine that
73
def __init__(self, func):
79
return 'main coroutine'
81
return 'coroutine for func ' + self.f.func_name
87
return cmp(id(x), id(y))
96
class Killed(Exception): pass
97
class EarlyExit(Exception): pass
101
self.active = self.main = _CoEvent(None)
102
self.invokedby = {self.main: None}
105
self.terminated_by = None
107
def create(self, func, *args):
109
self.invokedby[me] = None
110
thread.start_new_thread(self._start, (me,) + args)
113
def _start(self, me, *args):
123
self.terminated_by = me
128
raise TypeError, 'kill() called on dead coroutines'
130
for coroutine in self.invokedby.keys():
133
def back(self, data=None):
134
return self.tran( self.invokedby[self.active], data )
136
def detach(self, data=None):
137
return self.tran( self.main, data )
139
def tran(self, target, data=None):
140
if not self.invokedby.has_key(target):
141
raise TypeError, '.tran target %r is not an active coroutine' % (target,)
143
raise TypeError, '.tran target %r is killed' % (target,)
146
self.invokedby[target] = me
152
if self.main is not me:
154
if self.terminated_by is not None:
155
raise EarlyExit, '%r terminated early' % (self.terminated_by,)