PageRenderTime 42ms CodeModel.GetById 20ms app.highlight 8ms RepoModel.GetById 12ms app.codeStats 0ms

/Demo/threads/Coroutine.py

http://unladen-swallow.googlecode.com/
Python | 159 lines | 73 code | 18 blank | 68 comment | 16 complexity | 8d642e534c4c665a2da48b16a1cb0003 MD5 | raw file
  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
 69import thread
 70import sync
 71
 72class _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
 96class Killed(Exception): pass
 97class EarlyExit(Exception): pass
 98
 99class 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