mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			159 lines
		
	
	
	
		
			5.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			159 lines
		
	
	
	
		
			5.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # 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.__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:
 | |
|                     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 target not in self.invokedby:
 | |
|             raise TypeError('.tran target %r is not an active coroutine' % (target,))
 | |
|         if self.killed:
 | |
|             raise TypeError('.tran target %r is killed' % (target,))
 | |
|         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('%r terminated early' % (self.terminated_by,))
 | |
| 
 | |
|         return self.value
 | |
| 
 | |
| # end of module
 | 
