thread.py 8.0 KB

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