diff options
Diffstat (limited to 'Demo/threads/Coroutine.py')
-rw-r--r-- | Demo/threads/Coroutine.py | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/Demo/threads/Coroutine.py b/Demo/threads/Coroutine.py new file mode 100644 index 0000000..0cf9255 --- /dev/null +++ b/Demo/threads/Coroutine.py @@ -0,0 +1,160 @@ +# Coroutine implementation using Python threads. +# +# Combines ideas from Guido's Generator module, and from the coroutine +# features of Icon and Simula 67. +# +# To run a collection of functions as coroutines, you need to create +# a Coroutine object to control them: +# co = Coroutine() +# and then 'create' a subsidiary object for each function in the +# collection: +# cof1 = co.create(f1 [, arg1, arg2, ...]) # [] means optional, +# cof2 = co.create(f2 [, arg1, arg2, ...]) #... not list +# cof3 = co.create(f3 [, arg1, arg2, ...]) +# etc. The functions need not be distinct; 'create'ing the same +# function multiple times gives you independent instances of the +# function. +# +# To start the coroutines running, use co.tran on one of the create'd +# functions; e.g., co.tran(cof2). The routine that first executes +# co.tran is called the "main coroutine". It's special in several +# respects: it existed before you created the Coroutine object; if any of +# the create'd coroutines exits (does a return, or suffers an unhandled +# exception), EarlyExit error is raised in the main coroutine; and the +# co.detach() method transfers control directly to the main coroutine +# (you can't use co.tran() for this because the main coroutine doesn't +# have a name ...). +# +# Coroutine objects support these methods: +# +# handle = .create(func [, arg1, arg2, ...]) +# Creates a coroutine for an invocation of func(arg1, arg2, ...), +# and returns a handle ("name") for the coroutine so created. The +# handle can be used as the target in a subsequent .tran(). +# +# .tran(target, data=None) +# Transfer control to the create'd coroutine "target", optionally +# passing it an arbitrary piece of data. To the coroutine A that does +# the .tran, .tran acts like an ordinary function call: another +# coroutine B can .tran back to it later, and if it does A's .tran +# returns the 'data' argument passed to B's tran. E.g., +# +# in coroutine coA in coroutine coC in coroutine coB +# x = co.tran(coC) co.tran(coB) co.tran(coA,12) +# print x # 12 +# +# The data-passing feature is taken from Icon, and greatly cuts +# the need to use global variables for inter-coroutine communication. +# +# .back( data=None ) +# The same as .tran(invoker, data=None), where 'invoker' is the +# coroutine that most recently .tran'ed control to the coroutine +# doing the .back. This is akin to Icon's "&source". +# +# .detach( data=None ) +# The same as .tran(main, data=None), where 'main' is the +# (unnameable!) coroutine that started it all. 'main' has all the +# rights of any other coroutine: upon receiving control, it can +# .tran to an arbitrary coroutine of its choosing, go .back to +# the .detach'er, or .kill the whole thing. +# +# .kill() +# Destroy all the coroutines, and return control to the main +# coroutine. None of the create'ed coroutines can be resumed after a +# .kill(). An EarlyExit exception does a .kill() automatically. It's +# a good idea to .kill() coroutines you're done with, since the +# current implementation consumes a thread for each coroutine that +# may be resumed. + +import thread +import sync + +class _CoEvent: + def __init__(self, func): + self.f = func + self.e = sync.event() + + def __repr__(self): + if self.f is None: + return 'main coroutine' + else: + return 'coroutine for func ' + self.f.func_name + + def __hash__(self): + return id(self) + + def __cmp__(x,y): + return cmp(id(x), id(y)) + + def resume(self): + self.e.post() + + def wait(self): + self.e.wait() + self.e.clear() + +Killed = 'Coroutine.Killed' +EarlyExit = 'Coroutine.EarlyExit' + +class Coroutine: + def __init__(self): + self.active = self.main = _CoEvent(None) + self.invokedby = {self.main: None} + self.killed = 0 + self.value = None + self.terminated_by = None + + def create(self, func, *args): + me = _CoEvent(func) + self.invokedby[me] = None + thread.start_new_thread(self._start, (me,) + args) + return me + + def _start(self, me, *args): + me.wait() + if not self.killed: + try: + try: + apply(me.f, args) + except Killed: + pass + finally: + if not self.killed: + self.terminated_by = me + self.kill() + + def kill(self): + if self.killed: + raise TypeError, 'kill() called on dead coroutines' + self.killed = 1 + for coroutine in self.invokedby.keys(): + coroutine.resume() + + def back(self, data=None): + return self.tran( self.invokedby[self.active], data ) + + def detach(self, data=None): + return self.tran( self.main, data ) + + def tran(self, target, data=None): + if not self.invokedby.has_key(target): + raise TypeError, '.tran target ' + `target` + \ + ' is not an active coroutine' + if self.killed: + raise TypeError, '.tran target ' + `target` + ' is killed' + self.value = data + me = self.active + self.invokedby[target] = me + self.active = target + target.resume() + + me.wait() + if self.killed: + if self.main is not me: + raise Killed + if self.terminated_by is not None: + raise EarlyExit, `self.terminated_by` + ' terminated early' + + return self.value + +# end of module |