thread.odin 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. package thread
  2. import "base:runtime"
  3. import "core:mem"
  4. import "base:intrinsics"
  5. _ :: intrinsics
  6. /*
  7. Value, specifying whether `core:thread` functionality is available on the
  8. current platform.
  9. */
  10. IS_SUPPORTED :: _IS_SUPPORTED
  11. /*
  12. Type for a procedure that will be run in a thread, after that thread has been
  13. started.
  14. */
  15. Thread_Proc :: #type proc(^Thread)
  16. /*
  17. Maximum number of user arguments for polymorphic thread procedures.
  18. */
  19. MAX_USER_ARGUMENTS :: 8
  20. /*
  21. Type representing the state/flags of the thread.
  22. */
  23. Thread_State :: enum u8 {
  24. Started,
  25. Joined,
  26. Done,
  27. Self_Cleanup,
  28. }
  29. /*
  30. Type representing a thread handle and the associated with that thread data.
  31. */
  32. Thread :: struct {
  33. using specific: Thread_Os_Specific,
  34. flags: bit_set[Thread_State; u8],
  35. // Thread ID.
  36. id: int,
  37. // The thread procedure.
  38. procedure: Thread_Proc,
  39. // User-supplied pointer, that will be available to the thread once it is
  40. // started. Should be set after the thread has been created, but before
  41. // it is started.
  42. data: rawptr,
  43. // User-supplied integer, that will be available to the thread once it is
  44. // started. Should be set after the thread has been created, but before
  45. // it is started.
  46. user_index: int,
  47. // User-supplied array of arguments, that will be available to the thread,
  48. // once it is started. Should be set after the thread has been created,
  49. // but before it is started.
  50. user_args: [MAX_USER_ARGUMENTS]rawptr,
  51. // The thread context.
  52. // This field can be assigned to directly, after the thread has been
  53. // created, but __before__ the thread has been started. This field must
  54. // not be changed after the thread has started.
  55. //
  56. // **Note**: If this field is **not** set, the temp allocator will be managed
  57. // automatically. If it is set, the allocators must be handled manually.
  58. //
  59. // **IMPORTANT**:
  60. // By default, the thread proc will get the same context as `main()` gets.
  61. // In this situation, the thread will get a new temporary allocator which
  62. // will be cleaned up when the thread dies. ***This does NOT happen when
  63. // `init_context` field is initialized***.
  64. //
  65. // If `init_context` is initialized, and `temp_allocator` field is set to
  66. // the default temp allocator, then `runtime.default_temp_allocator_destroy()`
  67. // procedure needs to be called from the thread procedure, in order to prevent
  68. // any memory leaks.
  69. init_context: Maybe(runtime.Context),
  70. // The allocator used to allocate data for the thread.
  71. creation_allocator: mem.Allocator,
  72. }
  73. when IS_SUPPORTED {
  74. #assert(size_of(Thread{}.user_index) == size_of(uintptr))
  75. }
  76. /*
  77. Type representing priority of a thread.
  78. */
  79. Thread_Priority :: enum {
  80. Normal,
  81. Low,
  82. High,
  83. }
  84. /*
  85. Create a thread in a suspended state with the given priority.
  86. This procedure creates a thread that will be set to run the procedure
  87. specified by `procedure` parameter with a specified priority. The returned
  88. thread will be in a suspended state, until `start()` procedure is called.
  89. To start the thread, call `start()`. Also the `create_and_start()`
  90. procedure can be called to create and start the thread immediately.
  91. */
  92. create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread {
  93. return _create(procedure, priority)
  94. }
  95. /*
  96. Wait for the thread to finish and free all data associated with it.
  97. */
  98. destroy :: proc(thread: ^Thread) {
  99. _destroy(thread)
  100. }
  101. /*
  102. Start a suspended thread.
  103. */
  104. start :: proc(thread: ^Thread) {
  105. _start(thread)
  106. }
  107. /*
  108. Check if the thread has finished work.
  109. */
  110. is_done :: proc(thread: ^Thread) -> bool {
  111. return _is_done(thread)
  112. }
  113. /*
  114. Wait for the thread to finish work.
  115. */
  116. join :: proc(thread: ^Thread) {
  117. _join(thread)
  118. }
  119. /*
  120. Wait for all threads to finish work.
  121. */
  122. join_multiple :: proc(threads: ..^Thread) {
  123. _join_multiple(..threads)
  124. }
  125. /*
  126. Forcibly terminate a running thread.
  127. */
  128. terminate :: proc(thread: ^Thread, exit_code: int) {
  129. _terminate(thread, exit_code)
  130. }
  131. /*
  132. Yield the execution of the current thread to another OS thread or process.
  133. */
  134. yield :: proc() {
  135. _yield()
  136. }
  137. /*
  138. Run a procedure on a different thread.
  139. This procedure runs the given procedure on another thread. The context
  140. specified by `init_context` will be used as the context in which `fn` is going
  141. to execute. The thread will have priority specified by the `priority` parameter.
  142. **IMPORTANT**: If `init_context` is specified and the default temporary allocator
  143. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
  144. in order to free the resources associated with the temporary allocations.
  145. */
  146. run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) {
  147. create_and_start(fn, init_context, priority, true)
  148. }
  149. /*
  150. Run a procedure with one pointer parameter on a different thread.
  151. This procedure runs the given procedure on another thread. The context
  152. specified by `init_context` will be used as the context in which `fn` is going
  153. to execute. The thread will have priority specified by the `priority` parameter.
  154. **IMPORTANT**: If `init_context` is specified and the default temporary allocator
  155. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
  156. in order to free the resources associated with the temporary allocations.
  157. */
  158. run_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) {
  159. create_and_start_with_data(data, fn, init_context, priority, true)
  160. }
  161. /*
  162. Run a procedure with one polymorphic parameter on a different thread.
  163. This procedure runs the given procedure on another thread. The context
  164. specified by `init_context` will be used as the context in which `fn` is going
  165. to execute. The thread will have priority specified by the `priority` parameter.
  166. **IMPORTANT**: If `init_context` is specified and the default temporary allocator
  167. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
  168. in order to free the resources associated with the temporary allocations.
  169. */
  170. run_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal)
  171. where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
  172. create_and_start_with_poly_data(data, fn, init_context, priority, true)
  173. }
  174. /*
  175. Run a procedure with two polymorphic parameters on a different thread.
  176. This procedure runs the given procedure on another thread. The context
  177. specified by `init_context` will be used as the context in which `fn` is going
  178. to execute. The thread will have priority specified by the `priority` parameter.
  179. **IMPORTANT**: If `init_context` is specified and the default temporary allocator
  180. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
  181. in order to free the resources associated with the temporary allocations.
  182. */
  183. run_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal)
  184. where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
  185. create_and_start_with_poly_data2(arg1, arg2, fn, init_context, priority, true)
  186. }
  187. /*
  188. Run a procedure with three polymorphic parameters on a different thread.
  189. This procedure runs the given procedure on another thread. The context
  190. specified by `init_context` will be used as the context in which `fn` is going
  191. to execute. The thread will have priority specified by the `priority` parameter.
  192. **IMPORTANT**: If `init_context` is specified and the default temporary allocator
  193. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
  194. in order to free the resources associated with the temporary allocations.
  195. */
  196. 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)
  197. where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
  198. create_and_start_with_poly_data3(arg1, arg2, arg3, fn, init_context, priority, true)
  199. }
  200. /*
  201. Run a procedure with four polymorphic parameters on a different thread.
  202. This procedure runs the given procedure on another thread. The context
  203. specified by `init_context` will be used as the context in which `fn` is going
  204. to execute. The thread will have priority specified by the `priority` parameter.
  205. **IMPORTANT**: If `init_context` is specified and the default temporary allocator
  206. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
  207. in order to free the resources associated with the temporary allocations.
  208. */
  209. 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)
  210. where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
  211. create_and_start_with_poly_data4(arg1, arg2, arg3, arg4, fn, init_context, priority, true)
  212. }
  213. /*
  214. Run a procedure on a different thread.
  215. This procedure runs the given procedure on another thread. The context
  216. specified by `init_context` will be used as the context in which `fn` is going
  217. to execute. The thread will have priority specified by the `priority` parameter.
  218. If `self_cleanup` is specified, after the thread finishes the execution of the
  219. `fn` procedure, the resources associated with the thread are going to be
  220. automatically freed. **Do not** dereference the `^Thread` pointer, if this
  221. flag is specified.
  222. **IMPORTANT**: If `init_context` is specified and the default temporary allocator
  223. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
  224. in order to free the resources associated with the temporary allocations.
  225. */
  226. create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread {
  227. thread_proc :: proc(t: ^Thread) {
  228. fn := cast(proc())t.data
  229. fn()
  230. }
  231. t := create(thread_proc, priority)
  232. t.data = rawptr(fn)
  233. if self_cleanup {
  234. intrinsics.atomic_or(&t.flags, {.Self_Cleanup})
  235. }
  236. t.init_context = init_context
  237. start(t)
  238. return t
  239. }
  240. /*
  241. Run a procedure with one pointer parameter on a different thread.
  242. This procedure runs the given procedure on another thread. The context
  243. specified by `init_context` will be used as the context in which `fn` is going
  244. to execute. The thread will have priority specified by the `priority` parameter.
  245. If `self_cleanup` is specified, after the thread finishes the execution of the
  246. `fn` procedure, the resources associated with the thread are going to be
  247. automatically freed. **Do not** dereference the `^Thread` pointer, if this
  248. flag is specified.
  249. **IMPORTANT**: If `init_context` is specified and the default temporary allocator
  250. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
  251. in order to free the resources associated with the temporary allocations.
  252. */
  253. create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread {
  254. thread_proc :: proc(t: ^Thread) {
  255. fn := cast(proc(rawptr))t.data
  256. assert(t.user_index >= 1)
  257. data := t.user_args[0]
  258. fn(data)
  259. }
  260. t := create(thread_proc, priority)
  261. t.data = rawptr(fn)
  262. t.user_index = 1
  263. t.user_args[0] = data
  264. if self_cleanup {
  265. intrinsics.atomic_or(&t.flags, {.Self_Cleanup})
  266. }
  267. t.init_context = init_context
  268. start(t)
  269. return t
  270. }
  271. /*
  272. Run a procedure with one polymorphic parameter on a different thread.
  273. This procedure runs the given procedure on another thread. The context
  274. specified by `init_context` will be used as the context in which `fn` is going
  275. to execute. The thread will have priority specified by the `priority` parameter.
  276. If `self_cleanup` is specified, after the thread finishes the execution of the
  277. `fn` procedure, the resources associated with the thread are going to be
  278. automatically freed. **Do not** dereference the `^Thread` pointer, if this
  279. flag is specified.
  280. **IMPORTANT**: If `init_context` is specified and the default temporary allocator
  281. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
  282. in order to free the resources associated with the temporary allocations.
  283. */
  284. create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread
  285. where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
  286. thread_proc :: proc(t: ^Thread) {
  287. fn := cast(proc(T))t.data
  288. assert(t.user_index >= 1)
  289. data := (^T)(&t.user_args[0])^
  290. fn(data)
  291. }
  292. t := create(thread_proc, priority)
  293. t.data = rawptr(fn)
  294. t.user_index = 1
  295. data := data
  296. mem.copy(&t.user_args[0], &data, size_of(T))
  297. if self_cleanup {
  298. intrinsics.atomic_or(&t.flags, {.Self_Cleanup})
  299. }
  300. t.init_context = init_context
  301. start(t)
  302. return t
  303. }
  304. /*
  305. Run a procedure with two polymorphic parameters on a different thread.
  306. This procedure runs the given procedure on another thread. The context
  307. specified by `init_context` will be used as the context in which `fn` is going
  308. to execute. The thread will have priority specified by the `priority` parameter.
  309. If `self_cleanup` is specified, after the thread finishes the execution of the
  310. `fn` procedure, the resources associated with the thread are going to be
  311. automatically freed. **Do not** dereference the `^Thread` pointer, if this
  312. flag is specified.
  313. **IMPORTANT**: If `init_context` is specified and the default temporary allocator
  314. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
  315. in order to free the resources associated with the temporary allocations.
  316. */
  317. 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, self_cleanup := false) -> ^Thread
  318. where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
  319. thread_proc :: proc(t: ^Thread) {
  320. fn := cast(proc(T1, T2))t.data
  321. assert(t.user_index >= 2)
  322. user_args := mem.slice_to_bytes(t.user_args[:])
  323. arg1 := (^T1)(raw_data(user_args))^
  324. arg2 := (^T2)(raw_data(user_args[size_of(T1):]))^
  325. fn(arg1, arg2)
  326. }
  327. t := create(thread_proc, priority)
  328. t.data = rawptr(fn)
  329. t.user_index = 2
  330. arg1, arg2 := arg1, arg2
  331. user_args := mem.slice_to_bytes(t.user_args[:])
  332. n := copy(user_args, mem.ptr_to_bytes(&arg1))
  333. _ = copy(user_args[n:], mem.ptr_to_bytes(&arg2))
  334. if self_cleanup {
  335. intrinsics.atomic_or(&t.flags, {.Self_Cleanup})
  336. }
  337. t.init_context = init_context
  338. start(t)
  339. return t
  340. }
  341. /*
  342. Run a procedure with three polymorphic parameters on a different thread.
  343. This procedure runs the given procedure on another thread. The context
  344. specified by `init_context` will be used as the context in which `fn` is going
  345. to execute. The thread will have priority specified by the `priority` parameter.
  346. If `self_cleanup` is specified, after the thread finishes the execution of the
  347. `fn` procedure, the resources associated with the thread are going to be
  348. automatically freed. **Do not** dereference the `^Thread` pointer, if this
  349. flag is specified.
  350. **IMPORTANT**: If `init_context` is specified and the default temporary allocator
  351. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
  352. in order to free the resources associated with the temporary allocations.
  353. */
  354. 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, self_cleanup := false) -> ^Thread
  355. where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
  356. thread_proc :: proc(t: ^Thread) {
  357. fn := cast(proc(T1, T2, T3))t.data
  358. assert(t.user_index >= 3)
  359. user_args := mem.slice_to_bytes(t.user_args[:])
  360. arg1 := (^T1)(raw_data(user_args))^
  361. arg2 := (^T2)(raw_data(user_args[size_of(T1):]))^
  362. arg3 := (^T3)(raw_data(user_args[size_of(T1) + size_of(T2):]))^
  363. fn(arg1, arg2, arg3)
  364. }
  365. t := create(thread_proc, priority)
  366. t.data = rawptr(fn)
  367. t.user_index = 3
  368. arg1, arg2, arg3 := arg1, arg2, arg3
  369. user_args := mem.slice_to_bytes(t.user_args[:])
  370. n := copy(user_args, mem.ptr_to_bytes(&arg1))
  371. n += copy(user_args[n:], mem.ptr_to_bytes(&arg2))
  372. _ = copy(user_args[n:], mem.ptr_to_bytes(&arg3))
  373. if self_cleanup {
  374. intrinsics.atomic_or(&t.flags, {.Self_Cleanup})
  375. }
  376. t.init_context = init_context
  377. start(t)
  378. return t
  379. }
  380. /*
  381. Run a procedure with four polymorphic parameters on a different thread.
  382. This procedure runs the given procedure on another thread. The context
  383. specified by `init_context` will be used as the context in which `fn` is going
  384. to execute. The thread will have priority specified by the `priority` parameter.
  385. If `self_cleanup` is specified, after the thread finishes the execution of the
  386. `fn` procedure, the resources associated with the thread are going to be
  387. automatically freed. **Do not** dereference the `^Thread` pointer, if this
  388. flag is specified.
  389. **IMPORTANT**: If `init_context` is specified and the default temporary allocator
  390. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
  391. in order to free the resources associated with the temporary allocations.
  392. */
  393. 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, self_cleanup := false) -> ^Thread
  394. where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
  395. thread_proc :: proc(t: ^Thread) {
  396. fn := cast(proc(T1, T2, T3, T4))t.data
  397. assert(t.user_index >= 4)
  398. user_args := mem.slice_to_bytes(t.user_args[:])
  399. arg1 := (^T1)(raw_data(user_args))^
  400. arg2 := (^T2)(raw_data(user_args[size_of(T1):]))^
  401. arg3 := (^T3)(raw_data(user_args[size_of(T1) + size_of(T2):]))^
  402. arg4 := (^T4)(raw_data(user_args[size_of(T1) + size_of(T2) + size_of(T3):]))^
  403. fn(arg1, arg2, arg3, arg4)
  404. }
  405. t := create(thread_proc, priority)
  406. t.data = rawptr(fn)
  407. t.user_index = 4
  408. arg1, arg2, arg3, arg4 := arg1, arg2, arg3, arg4
  409. user_args := mem.slice_to_bytes(t.user_args[:])
  410. n := copy(user_args, mem.ptr_to_bytes(&arg1))
  411. n += copy(user_args[n:], mem.ptr_to_bytes(&arg2))
  412. n += copy(user_args[n:], mem.ptr_to_bytes(&arg3))
  413. _ = copy(user_args[n:], mem.ptr_to_bytes(&arg4))
  414. if self_cleanup {
  415. intrinsics.atomic_or(&t.flags, {.Self_Cleanup})
  416. }
  417. t.init_context = init_context
  418. start(t)
  419. return t
  420. }
  421. _select_context_for_thread :: proc(init_context: Maybe(runtime.Context)) -> runtime.Context {
  422. ctx, ok := init_context.?
  423. if !ok {
  424. return runtime.default_context()
  425. }
  426. /*
  427. NOTE(tetra, 2023-05-31):
  428. Ensure that the temp allocator is thread-safe when the user provides a specific initial context to use.
  429. Without this, the thread will use the same temp allocator state as the parent thread, and thus, bork it up.
  430. */
  431. if ctx.temp_allocator.procedure == runtime.default_temp_allocator_proc {
  432. ctx.temp_allocator.data = &runtime.global_default_temp_allocator_data
  433. }
  434. return ctx
  435. }
  436. _maybe_destroy_default_temp_allocator :: proc(init_context: Maybe(runtime.Context)) {
  437. if init_context != nil {
  438. // NOTE(tetra, 2023-05-31): If the user specifies a custom context for the thread,
  439. // then it's entirely up to them to handle whatever allocators they're using.
  440. return
  441. }
  442. if context.temp_allocator.procedure == runtime.default_temp_allocator_proc {
  443. runtime.default_temp_allocator_destroy(auto_cast context.temp_allocator.data)
  444. }
  445. }