thread.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. """ This module reimplements Python's native thread module using Panda
  2. threading constructs. It's designed as a drop-in replacement for the
  3. thread module for code that works with Panda; it is necessary because
  4. in some compilation models, Panda's threading constructs are
  5. incompatible with the OS-provided threads used by Python's thread
  6. module. """
  7. __all__ = [
  8. 'error', 'LockType',
  9. 'start_new_thread',
  10. 'interrupt_main',
  11. 'exit', 'allocate_lock', 'get_ident',
  12. 'stack_size',
  13. 'forceYield', 'considerYield',
  14. ]
  15. from panda3d import core
  16. # These methods are defined in Panda, and are particularly useful if
  17. # you may be running in Panda's SIMPLE_THREADS compilation mode.
  18. forceYield = core.Thread.forceYield
  19. considerYield = core.Thread.considerYield
  20. class error(Exception):
  21. pass
  22. class LockType:
  23. """ Implements a mutex lock. Instead of directly subclassing
  24. PandaModules.Mutex, we reimplement the lock here, to allow us to
  25. provide the described Python lock semantics. In particular, this
  26. allows a different thread to release the lock than the one that
  27. acquired it. """
  28. def __init__(self):
  29. self.__lock = core.Mutex('PythonLock')
  30. self.__cvar = core.ConditionVar(self.__lock)
  31. self.__locked = False
  32. def acquire(self, waitflag = 1):
  33. self.__lock.acquire()
  34. try:
  35. if self.__locked and not waitflag:
  36. return False
  37. while self.__locked:
  38. self.__cvar.wait()
  39. self.__locked = True
  40. return True
  41. finally:
  42. self.__lock.release()
  43. def release(self):
  44. self.__lock.acquire()
  45. try:
  46. if not self.__locked:
  47. raise error('Releasing unheld lock.')
  48. self.__locked = False
  49. self.__cvar.notify()
  50. finally:
  51. self.__lock.release()
  52. def locked(self):
  53. return self.__locked
  54. __enter__ = acquire
  55. def __exit__(self, t, v, tb):
  56. self.release()
  57. # Helper to generate new thread names
  58. _counter = 0
  59. def _newname(template="Thread-%d"):
  60. global _counter
  61. _counter = _counter + 1
  62. return template % _counter
  63. _threads = {}
  64. _nextThreadId = 0
  65. _threadsLock = core.Mutex('thread._threadsLock')
  66. def start_new_thread(function, args, kwargs = {}, name = None):
  67. def threadFunc(threadId, function = function, args = args, kwargs = kwargs):
  68. try:
  69. try:
  70. function(*args, **kwargs)
  71. except SystemExit:
  72. pass
  73. finally:
  74. _remove_thread_id(threadId)
  75. global _nextThreadId
  76. _threadsLock.acquire()
  77. try:
  78. threadId = _nextThreadId
  79. _nextThreadId += 1
  80. if name is None:
  81. name = 'PythonThread-%s' % (threadId)
  82. thread = core.PythonThread(threadFunc, [threadId], name, name)
  83. thread.setPythonData(threadId)
  84. _threads[threadId] = (thread, {}, None)
  85. thread.start(core.TPNormal, False)
  86. return threadId
  87. finally:
  88. _threadsLock.release()
  89. def _add_thread(thread, wrapper):
  90. """ Adds the indicated core.Thread object, with the indicated Python
  91. wrapper, to the thread list. Returns the new thread ID. """
  92. global _nextThreadId
  93. _threadsLock.acquire()
  94. try:
  95. threadId = _nextThreadId
  96. _nextThreadId += 1
  97. thread.setPythonData(threadId)
  98. _threads[threadId] = (thread, {}, wrapper)
  99. return threadId
  100. finally:
  101. _threadsLock.release()
  102. def _get_thread_wrapper(thread, wrapperClass):
  103. """ Returns the thread wrapper for the indicated thread. If there
  104. is not one, creates an instance of the indicated wrapperClass
  105. instead. """
  106. threadId = thread.getPythonData()
  107. if threadId is None:
  108. # The thread has never been assigned a threadId. Go assign one.
  109. global _nextThreadId
  110. _threadsLock.acquire()
  111. try:
  112. threadId = _nextThreadId
  113. _nextThreadId += 1
  114. thread.setPythonData(threadId)
  115. wrapper = wrapperClass(thread, threadId)
  116. _threads[threadId] = (thread, {}, wrapper)
  117. return wrapper
  118. finally:
  119. _threadsLock.release()
  120. else:
  121. # The thread has been assigned a threadId. Look for the wrapper.
  122. _threadsLock.acquire()
  123. try:
  124. t, locals, wrapper = _threads[threadId]
  125. assert t == thread
  126. if wrapper is None:
  127. wrapper = wrapperClass(thread, threadId)
  128. _threads[threadId] = (thread, locals, wrapper)
  129. return wrapper
  130. finally:
  131. _threadsLock.release()
  132. def _get_thread_locals(thread, i):
  133. """ Returns the locals dictionary for the indicated thread. If
  134. there is not one, creates an empty dictionary. """
  135. threadId = thread.getPythonData()
  136. if threadId is None:
  137. # The thread has never been assigned a threadId. Go assign one.
  138. global _nextThreadId
  139. _threadsLock.acquire()
  140. try:
  141. threadId = _nextThreadId
  142. _nextThreadId += 1
  143. thread.setPythonData(threadId)
  144. locals = {}
  145. _threads[threadId] = (thread, locals, None)
  146. return locals.setdefault(i, {})
  147. finally:
  148. _threadsLock.release()
  149. else:
  150. # The thread has been assigned a threadId. Get the locals.
  151. _threadsLock.acquire()
  152. try:
  153. t, locals, wrapper = _threads[threadId]
  154. assert t == thread
  155. return locals.setdefault(i, {})
  156. finally:
  157. _threadsLock.release()
  158. def _remove_thread_id(threadId):
  159. """ Removes the thread with the indicated ID from the thread list. """
  160. _threadsLock.acquire()
  161. try:
  162. thread, locals, wrapper = _threads[threadId]
  163. assert thread.getPythonData() == threadId
  164. del _threads[threadId]
  165. thread.setPythonData(None)
  166. finally:
  167. _threadsLock.release()
  168. def interrupt_main():
  169. # TODO.
  170. pass
  171. def exit():
  172. raise SystemExit
  173. def allocate_lock():
  174. return LockType()
  175. def get_ident():
  176. return core.Thread.getCurrentThread().this
  177. def stack_size(size = 0):
  178. raise error
  179. class _local(object):
  180. """ This class provides local thread storage using Panda's
  181. threading system. """
  182. def __del__(self):
  183. i = id(self)
  184. # Delete this key from all threads.
  185. _threadsLock.acquire()
  186. try:
  187. for thread, locals, wrapper in _threads.values():
  188. try:
  189. del locals[i]
  190. except KeyError:
  191. pass
  192. finally:
  193. _threadsLock.release()
  194. def __setattr__(self, key, value):
  195. d = _get_thread_locals(core.Thread.getCurrentThread(), id(self))
  196. d[key] = value
  197. ## def __getattr__(self, key):
  198. ## d = _get_thread_locals(core.Thread.getCurrentThread(), id(self))
  199. ## try:
  200. ## return d[key]
  201. ## except KeyError:
  202. ## raise AttributeError
  203. def __getattribute__(self, key):
  204. d = _get_thread_locals(core.Thread.getCurrentThread(), id(self))
  205. if key == '__dict__':
  206. return d
  207. try:
  208. return d[key]
  209. except KeyError:
  210. return object.__getattribute__(self, key)