thread.py 7.6 KB

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