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
,)