thread.odin 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. package thread
  2. import "core:runtime"
  3. import "core:mem"
  4. import "core:intrinsics"
  5. _ :: intrinsics
  6. Thread_Proc :: #type proc(^Thread)
  7. MAX_USER_ARGUMENTS :: 8
  8. Thread :: struct {
  9. using specific: Thread_Os_Specific,
  10. id: int,
  11. procedure: Thread_Proc,
  12. /*
  13. These are values that the user can set as they wish, after the thread has been created.
  14. This data is easily available to the thread proc.
  15. These fields can be assigned to directly.
  16. Should be set after the thread is created, but before it is started.
  17. */
  18. data: rawptr,
  19. user_index: int,
  20. user_args: [MAX_USER_ARGUMENTS]rawptr,
  21. /*
  22. The context to be used as 'context' in the thread proc.
  23. This field can be assigned to directly, after the thread has been created, but __before__ the thread has been started.
  24. This field must not be changed after the thread has started.
  25. NOTE: If you __don't__ set this, the temp allocator will be managed for you;
  26. If you __do__ set this, then you're expected to handle whatever allocators you set, yourself.
  27. IMPORTANT:
  28. By default, the thread proc will get the same context as `main()` gets.
  29. In this sitation, the thread will get a new temporary allocator which will be cleaned up when the thread dies.
  30. ***This does NOT happen when you set `init_context`.***
  31. This means that if you set `init_context`, but still have the `temp_allocator` field set to the default temp allocator,
  32. then you'll need to call `runtime.default_temp_allocator_destroy(auto_cast the_thread.init_context.temp_allocator.data)` manually,
  33. in order to prevent any memory leaks.
  34. This call ***must*** be done ***in the thread proc*** because the default temporary allocator uses thread local state!
  35. */
  36. init_context: Maybe(runtime.Context),
  37. creation_allocator: mem.Allocator,
  38. }
  39. #assert(size_of(Thread{}.user_index) == size_of(uintptr))
  40. Thread_Priority :: enum {
  41. Normal,
  42. Low,
  43. High,
  44. }
  45. /*
  46. Creates a thread in a suspended state with the given priority.
  47. To start the thread, call `thread.start()`.
  48. See `thread.create_and_start()`.
  49. */
  50. create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread {
  51. return _create(procedure, priority)
  52. }
  53. destroy :: proc(thread: ^Thread) {
  54. _destroy(thread)
  55. }
  56. start :: proc(thread: ^Thread) {
  57. _start(thread)
  58. }
  59. is_done :: proc(thread: ^Thread) -> bool {
  60. return _is_done(thread)
  61. }
  62. join :: proc(thread: ^Thread) {
  63. _join(thread)
  64. }
  65. join_multiple :: proc(threads: ..^Thread) {
  66. _join_multiple(..threads)
  67. }
  68. terminate :: proc(thread: ^Thread, exit_code: int) {
  69. _terminate(thread, exit_code)
  70. }
  71. yield :: proc() {
  72. _yield()
  73. }
  74. run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) {
  75. thread_proc :: proc(t: ^Thread) {
  76. fn := cast(proc())t.data
  77. fn()
  78. destroy(t)
  79. }
  80. t := create(thread_proc, priority)
  81. t.data = rawptr(fn)
  82. t.init_context = init_context
  83. start(t)
  84. }
  85. run_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) {
  86. thread_proc :: proc(t: ^Thread) {
  87. fn := cast(proc(rawptr))t.data
  88. assert(t.user_index >= 1)
  89. data := t.user_args[0]
  90. fn(data)
  91. destroy(t)
  92. }
  93. t := create(thread_proc, priority)
  94. t.data = rawptr(fn)
  95. t.user_index = 1
  96. t.user_args = data
  97. t.init_context = init_context
  98. start(t)
  99. }
  100. run_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal)
  101. where size_of(T) <= size_of(rawptr) {
  102. thread_proc :: proc(t: ^Thread) {
  103. fn := cast(proc(T))t.data
  104. assert(t.user_index >= 1)
  105. data := (^T)(&t.user_args[0])^
  106. fn(data)
  107. destroy(t)
  108. }
  109. t := create(thread_proc, priority)
  110. t.data = rawptr(fn)
  111. t.user_index = 1
  112. data := data
  113. mem.copy(&t.user_args[0], &data, size_of(data))
  114. t.init_context = init_context
  115. start(t)
  116. }
  117. run_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal)
  118. where size_of(T1) <= size_of(rawptr),
  119. size_of(T2) <= size_of(rawptr) {
  120. thread_proc :: proc(t: ^Thread) {
  121. fn := cast(proc(T1, T2))t.data
  122. assert(t.user_index >= 2)
  123. arg1 := (^T1)(&t.user_args[0])^
  124. arg2 := (^T2)(&t.user_args[1])^
  125. fn(arg1, arg2)
  126. destroy(t)
  127. }
  128. t := create(thread_proc, priority)
  129. t.data = rawptr(fn)
  130. t.user_index = 2
  131. arg1, arg2 := arg1, arg2
  132. mem.copy(&t.user_args[0], &arg1, size_of(arg1))
  133. mem.copy(&t.user_args[1], &arg2, size_of(arg2))
  134. t.init_context = init_context
  135. start(t)
  136. }
  137. run_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal)
  138. where size_of(T1) <= size_of(rawptr),
  139. size_of(T2) <= size_of(rawptr),
  140. size_of(T3) <= size_of(rawptr) {
  141. thread_proc :: proc(t: ^Thread) {
  142. fn := cast(proc(T1, T2, T3))t.data
  143. assert(t.user_index >= 3)
  144. arg1 := (^T1)(&t.user_args[0])^
  145. arg2 := (^T2)(&t.user_args[1])^
  146. arg3 := (^T3)(&t.user_args[2])^
  147. fn(arg1, arg2, arg3)
  148. destroy(t)
  149. }
  150. t := create(thread_proc, priority)
  151. t.data = rawptr(fn)
  152. t.user_index = 3
  153. arg1, arg2, arg3 := arg1, arg2, arg3
  154. mem.copy(&t.user_args[0], &arg1, size_of(arg1))
  155. mem.copy(&t.user_args[1], &arg2, size_of(arg2))
  156. mem.copy(&t.user_args[2], &arg3, size_of(arg3))
  157. t.init_context = init_context
  158. start(t)
  159. }
  160. run_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal)
  161. where size_of(T1) <= size_of(rawptr),
  162. size_of(T2) <= size_of(rawptr),
  163. size_of(T3) <= size_of(rawptr) {
  164. thread_proc :: proc(t: ^Thread) {
  165. fn := cast(proc(T1, T2, T3, T4))t.data
  166. assert(t.user_index >= 4)
  167. arg1 := (^T1)(&t.user_args[0])^
  168. arg2 := (^T2)(&t.user_args[1])^
  169. arg3 := (^T3)(&t.user_args[2])^
  170. arg4 := (^T4)(&t.user_args[3])^
  171. fn(arg1, arg2, arg3, arg4)
  172. destroy(t)
  173. }
  174. t := create(thread_proc, priority)
  175. t.data = rawptr(fn)
  176. t.user_index = 4
  177. arg1, arg2, arg3, arg4 := arg1, arg2, arg3, arg4
  178. mem.copy(&t.user_args[0], &arg1, size_of(arg1))
  179. mem.copy(&t.user_args[1], &arg2, size_of(arg2))
  180. mem.copy(&t.user_args[2], &arg3, size_of(arg3))
  181. mem.copy(&t.user_args[3], &arg4, size_of(arg4))
  182. t.init_context = init_context
  183. start(t)
  184. }
  185. create_and_start :: proc(fn: Thread_Proc, init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) -> ^Thread {
  186. t := create(fn, priority)
  187. t.init_context = init_context
  188. start(t)
  189. return t
  190. }
  191. create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) -> ^Thread {
  192. thread_proc :: proc(t: ^Thread) {
  193. fn := cast(proc(rawptr))t.data
  194. assert(t.user_index >= 1)
  195. data := t.user_args[0]
  196. fn(data)
  197. }
  198. t := create(thread_proc, priority)
  199. t.data = rawptr(fn)
  200. t.user_index = 1
  201. t.user_args = data
  202. t.init_context = init_context
  203. start(t)
  204. return t
  205. }
  206. create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) -> ^Thread
  207. where size_of(T) <= size_of(rawptr) {
  208. thread_proc :: proc(t: ^Thread) {
  209. fn := cast(proc(T))t.data
  210. assert(t.user_index >= 1)
  211. data := (^T)(&t.user_args[0])^
  212. fn(data)
  213. }
  214. t := create(thread_proc, priority)
  215. t.data = rawptr(fn)
  216. t.user_index = 1
  217. data := data
  218. mem.copy(&t.user_args[0], &data, size_of(data))
  219. t.init_context = init_context
  220. start(t)
  221. return t
  222. }
  223. create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) -> ^Thread
  224. where size_of(T1) <= size_of(rawptr),
  225. size_of(T2) <= size_of(rawptr) {
  226. thread_proc :: proc(t: ^Thread) {
  227. fn := cast(proc(T1, T2))t.data
  228. assert(t.user_index >= 2)
  229. arg1 := (^T1)(&t.user_args[0])^
  230. arg2 := (^T2)(&t.user_args[1])^
  231. fn(arg1, arg2)
  232. }
  233. t := create(thread_proc, priority)
  234. t.data = rawptr(fn)
  235. t.user_index = 2
  236. arg1, arg2 := arg1, arg2
  237. mem.copy(&t.user_args[0], &arg1, size_of(arg1))
  238. mem.copy(&t.user_args[1], &arg2, size_of(arg2))
  239. t.init_context = init_context
  240. start(t)
  241. return t
  242. }
  243. create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) -> ^Thread
  244. where size_of(T1) <= size_of(rawptr),
  245. size_of(T2) <= size_of(rawptr),
  246. size_of(T3) <= size_of(rawptr) {
  247. thread_proc :: proc(t: ^Thread) {
  248. fn := cast(proc(T1, T2, T3))t.data
  249. assert(t.user_index >= 3)
  250. arg1 := (^T1)(&t.user_args[0])^
  251. arg2 := (^T2)(&t.user_args[1])^
  252. arg3 := (^T3)(&t.user_args[2])^
  253. fn(arg1, arg2, arg3)
  254. }
  255. t := create(thread_proc, priority)
  256. t.data = rawptr(fn)
  257. t.user_index = 3
  258. arg1, arg2, arg3 := arg1, arg2, arg3
  259. mem.copy(&t.user_args[0], &arg1, size_of(arg1))
  260. mem.copy(&t.user_args[1], &arg2, size_of(arg2))
  261. mem.copy(&t.user_args[2], &arg3, size_of(arg3))
  262. t.init_context = init_context
  263. start(t)
  264. return t
  265. }
  266. create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) -> ^Thread
  267. where size_of(T1) <= size_of(rawptr),
  268. size_of(T2) <= size_of(rawptr),
  269. size_of(T3) <= size_of(rawptr) {
  270. thread_proc :: proc(t: ^Thread) {
  271. fn := cast(proc(T1, T2, T3, T4))t.data
  272. assert(t.user_index >= 4)
  273. arg1 := (^T1)(&t.user_args[0])^
  274. arg2 := (^T2)(&t.user_args[1])^
  275. arg3 := (^T3)(&t.user_args[2])^
  276. arg4 := (^T4)(&t.user_args[3])^
  277. fn(arg1, arg2, arg3, arg4)
  278. }
  279. t := create(thread_proc, priority)
  280. t.data = rawptr(fn)
  281. t.user_index = 4
  282. arg1, arg2, arg3, arg4 := arg1, arg2, arg3, arg4
  283. mem.copy(&t.user_args[0], &arg1, size_of(arg1))
  284. mem.copy(&t.user_args[1], &arg2, size_of(arg2))
  285. mem.copy(&t.user_args[2], &arg3, size_of(arg3))
  286. mem.copy(&t.user_args[3], &arg4, size_of(arg4))
  287. t.init_context = init_context
  288. start(t)
  289. return t
  290. }
  291. _select_context_for_thread :: proc(init_context: Maybe(runtime.Context)) -> runtime.Context {
  292. ctx, ok := init_context.?
  293. if !ok {
  294. return runtime.default_context()
  295. }
  296. /*
  297. NOTE(tetra, 2023-05-31):
  298. Ensure that the temp allocator is thread-safe when the user provides a specific initial context to use.
  299. Without this, the thread will use the same temp allocator state as the parent thread, and thus, bork it up.
  300. */
  301. if ctx.temp_allocator.procedure == runtime.default_temp_allocator_proc {
  302. ctx.temp_allocator.data = &runtime.global_default_temp_allocator_data
  303. }
  304. return ctx
  305. }
  306. _maybe_destroy_default_temp_allocator :: proc(init_context: Maybe(runtime.Context)) {
  307. if init_context != nil {
  308. // NOTE(tetra, 2023-05-31): If the user specifies a custom context for the thread,
  309. // then it's entirely up to them to handle whatever allocators they're using.
  310. return
  311. }
  312. if context.temp_allocator.procedure == runtime.default_temp_allocator_proc {
  313. runtime.default_temp_allocator_destroy(auto_cast context.temp_allocator.data)
  314. }
  315. }