Home | History | Annotate | Download | only in threads
      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 class Killed(Exception): pass
     97 class EarlyExit(Exception): pass
     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 %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
    160