Adding basic imputil documentation.
[python.git] / Demo / threads / Coroutine.py
blob4cc65f7bf8d1eafa82b78e98257e2a782916b7ab
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:
8 # co = Coroutine()
9 # and then 'create' a subsidiary object for each function in the
10 # collection:
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
16 # function.
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
26 # have a name ...).
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)
44 # print x # 12
46 # The data-passing feature is taken from Icon, and greatly cuts
47 # the need to use global variables for inter-coroutine communication.
49 # .back( data=None )
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.
61 # .kill()
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
67 # may be resumed.
69 import thread
70 import sync
72 class _CoEvent:
73 def __init__(self, func):
74 self.f = func
75 self.e = sync.event()
77 def __repr__(self):
78 if self.f is None:
79 return 'main coroutine'
80 else:
81 return 'coroutine for func ' + self.f.func_name
83 def __hash__(self):
84 return id(self)
86 def __cmp__(x,y):
87 return cmp(id(x), id(y))
89 def resume(self):
90 self.e.post()
92 def wait(self):
93 self.e.wait()
94 self.e.clear()
96 Killed = 'Coroutine.Killed'
97 EarlyExit = 'Coroutine.EarlyExit'
99 class Coroutine:
100 def __init__(self):
101 self.active = self.main = _CoEvent(None)
102 self.invokedby = {self.main: None}
103 self.killed = 0
104 self.value = None
105 self.terminated_by = None
107 def create(self, func, *args):
108 me = _CoEvent(func)
109 self.invokedby[me] = None
110 thread.start_new_thread(self._start, (me,) + args)
111 return me
113 def _start(self, me, *args):
114 me.wait()
115 if not self.killed:
116 try:
117 try:
118 apply(me.f, args)
119 except Killed:
120 pass
121 finally:
122 if not self.killed:
123 self.terminated_by = me
124 self.kill()
126 def kill(self):
127 if self.killed:
128 raise TypeError, 'kill() called on dead coroutines'
129 self.killed = 1
130 for coroutine in self.invokedby.keys():
131 coroutine.resume()
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,)
142 if self.killed:
143 raise TypeError, '.tran target %r is killed' % (target,)
144 self.value = data
145 me = self.active
146 self.invokedby[target] = me
147 self.active = target
148 target.resume()
150 me.wait()
151 if self.killed:
152 if self.main is not me:
153 raise Killed
154 if self.terminated_by is not None:
155 raise EarlyExit, '%r terminated early' % (self.terminated_by,)
157 return self.value
159 # end of module