summaryrefslogtreecommitdiffstats
path: root/Demo/threads/Coroutine.py
diff options
context:
space:
mode:
Diffstat (limited to 'Demo/threads/Coroutine.py')
-rw-r--r--Demo/threads/Coroutine.py160
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