/Demo/threads/Coroutine.py

http://unladen-swallow.googlecode.com/ · Python · 159 lines · 73 code · 18 blank · 68 comment · 15 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. import thread
  69. import sync
  70. class _CoEvent:
  71. def __init__(self, func):
  72. self.f = func
  73. self.e = sync.event()
  74. def __repr__(self):
  75. if self.f is None:
  76. return 'main coroutine'
  77. else:
  78. return 'coroutine for func ' + self.f.func_name
  79. def __hash__(self):
  80. return id(self)
  81. def __cmp__(x,y):
  82. return cmp(id(x), id(y))
  83. def resume(self):
  84. self.e.post()
  85. def wait(self):
  86. self.e.wait()
  87. self.e.clear()
  88. class Killed(Exception): pass
  89. class EarlyExit(Exception): pass
  90. class Coroutine:
  91. def __init__(self):
  92. self.active = self.main = _CoEvent(None)
  93. self.invokedby = {self.main: None}
  94. self.killed = 0
  95. self.value = None
  96. self.terminated_by = None
  97. def create(self, func, *args):
  98. me = _CoEvent(func)
  99. self.invokedby[me] = None
  100. thread.start_new_thread(self._start, (me,) + args)
  101. return me
  102. def _start(self, me, *args):
  103. me.wait()
  104. if not self.killed:
  105. try:
  106. try:
  107. apply(me.f, args)
  108. except Killed:
  109. pass
  110. finally:
  111. if not self.killed:
  112. self.terminated_by = me
  113. self.kill()
  114. def kill(self):
  115. if self.killed:
  116. raise TypeError, 'kill() called on dead coroutines'
  117. self.killed = 1
  118. for coroutine in self.invokedby.keys():
  119. coroutine.resume()
  120. def back(self, data=None):
  121. return self.tran( self.invokedby[self.active], data )
  122. def detach(self, data=None):
  123. return self.tran( self.main, data )
  124. def tran(self, target, data=None):
  125. if not self.invokedby.has_key(target):
  126. raise TypeError, '.tran target %r is not an active coroutine' % (target,)
  127. if self.killed:
  128. raise TypeError, '.tran target %r is killed' % (target,)
  129. self.value = data
  130. me = self.active
  131. self.invokedby[target] = me
  132. self.active = target
  133. target.resume()
  134. me.wait()
  135. if self.killed:
  136. if self.main is not me:
  137. raise Killed
  138. if self.terminated_by is not None:
  139. raise EarlyExit, '%r terminated early' % (self.terminated_by,)
  140. return self.value
  141. # end of module