|
| 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 | +Killed = 'Coroutine.Killed' |
| 97 | +EarlyExit = 'Coroutine.EarlyExit' |
| 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 ' + `target` + \ |
| 142 | + ' is not an active coroutine' |
| 143 | + if self.killed: |
| 144 | + raise TypeError, '.tran target ' + `target` + ' is killed' |
| 145 | + self.value = data |
| 146 | + me = self.active |
| 147 | + self.invokedby[target] = me |
| 148 | + self.active = target |
| 149 | + target.resume() |
| 150 | + |
| 151 | + me.wait() |
| 152 | + if self.killed: |
| 153 | + if self.main is not me: |
| 154 | + raise Killed |
| 155 | + if self.terminated_by is not None: |
| 156 | + raise EarlyExit, `self.terminated_by` + ' terminated early' |
| 157 | + |
| 158 | + return self.value |
| 159 | + |
| 160 | +# end of module |
0 commit comments