Guido van Rossum | f4e13a4 | 2000-11-08 15:17:49 +0000 | [diff] [blame] | 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 |