thread.odin 20 KB

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