|
1 # Coroutine implementation using Python threads. |
|
2 # |
|
3 # Combines ideas from Guido's Generator module, and from the coroutine |
|
4 # features of Icon and Simula 67. |
|
5 # |
|
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. |
|
17 # |
|
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 ...). |
|
27 # |
|
28 # Coroutine objects support these methods: |
|
29 # |
|
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(). |
|
34 # |
|
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., |
|
41 # |
|
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 |
|
45 # |
|
46 # The data-passing feature is taken from Icon, and greatly cuts |
|
47 # the need to use global variables for inter-coroutine communication. |
|
48 # |
|
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". |
|
53 # |
|
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. |
|
60 # |
|
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. |
|
68 |
|
69 import thread |
|
70 import sync |
|
71 |
|
72 class _CoEvent: |
|
73 def __init__(self, func): |
|
74 self.f = func |
|
75 self.e = sync.event() |
|
76 |
|
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 |
|
82 |
|
83 def __hash__(self): |
|
84 return id(self) |
|
85 |
|
86 def __cmp__(x,y): |
|
87 return cmp(id(x), id(y)) |
|
88 |
|
89 def resume(self): |
|
90 self.e.post() |
|
91 |
|
92 def wait(self): |
|
93 self.e.wait() |
|
94 self.e.clear() |
|
95 |
|
96 class Killed(Exception): pass |
|
97 class EarlyExit(Exception): pass |
|
98 |
|
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 |
|
106 |
|
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 |
|
112 |
|
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() |
|
125 |
|
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() |
|
132 |
|
133 def back(self, data=None): |
|
134 return self.tran( self.invokedby[self.active], data ) |
|
135 |
|
136 def detach(self, data=None): |
|
137 return self.tran( self.main, data ) |
|
138 |
|
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() |
|
149 |
|
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,) |
|
156 |
|
157 return self.value |
|
158 |
|
159 # end of module |