core/thread
thread
Types
12Pool
Pool :: struct {
allocator: mem.Allocator,
mutex: sync.Mutex,
sem_available: sync.Sema,
// the following values are atomic
num_waiting: int,
num_in_processing: int,
num_outstanding: int,
num_done: int,
// end of atomics
// called once per thread at startup
thread_init_proc: Thread_Init_Proc,
thread_init_data: rawptr,
// called once per thread at shutdown
thread_fini_proc: Thread_Fini_Proc,
thread_fini_data: rawptr,
is_running: bool,
threads: []^Thread,
tasks: queue.Queue(Task),
tasks_done: [dynamic]Task,
}SourceDo not access the pool's members directly while the pool threads are running, since they use different kinds of locking and mutual exclusion devices. Careless access can and will lead to nasty bugs. Once initialized, the pool's memory address is not allowed to change until it is destroyed.
Pool_Thread_Data
Pool_Thread_Data :: struct {
pool: ^Pool,
task: Task,
}SourceTask
Task :: struct {
procedure: Task_Proc,
data: rawptr,
user_index: int,
allocator: mem.Allocator,
}SourceTask_Proc
Task_Proc :: proc(task: Task)SourceThread
Thread :: struct {
specific: Thread_Os_Specific,
flags: bit_set[Thread_State; u8],
// Thread ID. Depending on the platform, may start out as 0 (zero) until the thread
// has had a chance to run.
id: int,
// The thread procedure.
procedure: Thread_Proc,
// User-supplied pointer, that will be available to the thread once it is
// started. Should be set after the thread has been created, but before
// it is started.
data: rawptr,
// User-supplied integer, that will be available to the thread once it is
// started. Should be set after the thread has been created, but before
// it is started.
user_index: int,
// User-supplied array of arguments, that will be available to the thread,
// once it is started. Should be set after the thread has been created,
// but before it is started.
user_args: [8]rawptr,
// The thread context.
// This field can be assigned to directly, after the thread has been
// created, but __before__ the thread has been started. This field must
// not be changed after the thread has started.
//
// **Note**: If this field is **not** set, the temp allocator will be managed
// automatically. If it is set, the allocators must be handled manually.
//
// **IMPORTANT**:
// By default, the thread proc will get the same context as `main()` gets.
// In this situation, the thread will get a new temporary allocator which
// will be cleaned up when the thread dies. ***This does NOT happen when
// `init_context` field is initialized***.
//
// If `init_context` is initialized, and `temp_allocator` field is set to
// the default temp allocator, then `runtime.default_temp_allocator_destroy()`
// procedure needs to be called from the thread procedure, in order to prevent
// any memory leaks.
init_context: Maybe(runtime.Context),
// The allocator used to allocate data for the thread.
creation_allocator: mem.Allocator,
}SourceType representing a thread handle and the associated with that thread data.
Thread_Fini_Proc
Thread_Fini_Proc :: proc(thread: ^Thread, user_data: rawptr)SourceThread_Init_Proc
Thread_Init_Proc :: proc(thread: ^Thread, user_data: rawptr)SourceThread_Os_Specific
Thread_Os_Specific :: struct {}SourceThread_Os_Specific
Thread_Os_Specific :: struct {
unix_thread: posix.pthread_t,
start_ok: sync.Sema,
}SourceNOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t. Also see core/sys/darwin/mach_darwin.odin/semaphore_t.
Thread_Priority
Thread_Priority :: enum int {
Normal = 0,
Low = 1,
High = 2,
}SourceType representing priority of a thread.
Thread_Proc
Thread_Proc :: proc(^Thread)SourceType for a procedure that will be run in a thread, after that thread has been started.
Thread_State
Thread_State :: enum u8 {
Started = 0,
Joined = 1,
Done = 2,
Self_Cleanup = 3,
}SourceType representing the state/flags of the thread.
Constants
4IS_SUPPORTED
IS_SUPPORTED :: _IS_SUPPORTEDSourceValue, specifying whether core:thread functionality is available on the current platform.
MAX_USER_ARGUMENTS
MAX_USER_ARGUMENTS :: 8SourceMaximum number of user arguments for polymorphic thread procedures.
_IS_SUPPORTED
_IS_SUPPORTED :: falseSource_IS_SUPPORTED
_IS_SUPPORTED :: trueSourceVariables
1_thread_priority_map
_thread_priority_map :: [3]i32 = [Thread_Priority]i32{
.Normal = 0,
.Low = -2,
.High = +2,
}SourceProcedures
55_create
_create :: proc(procedure: Thread_Proc, priority = Thread_Priority.Normal) -> (^Thread)Source_create
_create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> (^Thread)SourceCreates a thread which will run the given procedure. It then waits for start to be called.
_destroy
_destroy :: proc(thread: ^Thread)Source_destroy
_destroy :: proc(t: ^Thread)Source_is_done
_is_done :: proc(t: ^Thread) -> (bool)Source_is_done
_is_done :: proc(t: ^Thread) -> (bool)Source_join
_join :: proc(t: ^Thread)Source_join
_join :: proc(t: ^Thread)Source_join_multiple
_join_multiple :: proc(threads)Source_join_multiple
_join_multiple :: proc(threads)Source_maybe_destroy_default_temp_allocator
_maybe_destroy_default_temp_allocator :: proc(init_context: Maybe(runtime.Context))Source_select_context_for_thread
_select_context_for_thread :: proc(init_context: Maybe(runtime.Context)) -> (runtime.Context)Source_start
_start :: proc(t: ^Thread)Source_start
_start :: proc(t: ^Thread)Source_terminate
_terminate :: proc(thread: ^Thread, exit_code: int)Source_terminate
_terminate :: proc(t: ^Thread, exit_code: int)Source_yield
_yield :: proc()Source_yield
_yield :: proc()Sourcecreate
create :: proc(procedure: Thread_Proc, priority = Thread_Priority.Normal) -> (^Thread)SourceCreate a thread in a suspended state with the given priority.
This procedure creates a thread that will be set to run the procedure specified by procedure parameter with a specified priority. The returned thread will be in a suspended state, until start() procedure is called.
To start the thread, call start(). Also the create_and_start() procedure can be called to create and start the thread immediately.
create_and_start
create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context), priority = Thread_Priority.Normal, self_cleanup: untyped boolean = false) -> (t: ^Thread)SourceRun a procedure on a different thread.
This procedure runs the given procedure on another thread. The context specified by init_context will be used as the context in which fn is going to execute. The thread will have priority specified by the priority parameter.
If self_cleanup is specified, after the thread finishes the execution of the fn procedure, the resources associated with the thread are going to be automatically freed.
Do not dereference the ^Thread pointer, if this flag is specified. That includes calling join, which needs to dereference ^Thread`.
IMPORTANT: If init_context is specified and the default temporary allocator is used, the thread procedure needs to call runtime.default_temp_allocator_destroy() in order to free the resources associated with the temporary allocations.
create_and_start_with_data
create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context), priority = Thread_Priority.Normal, self_cleanup: untyped boolean = false) -> (t: ^Thread)SourceRun a procedure with one pointer parameter on a different thread.
This procedure runs the given procedure on another thread. The context specified by init_context will be used as the context in which fn is going to execute. The thread will have priority specified by the priority parameter.
If self_cleanup is specified, after the thread finishes the execution of the fn procedure, the resources associated with the thread are going to be automatically freed.
Do not dereference the ^Thread pointer, if this flag is specified. That includes calling join, which needs to dereference ^Thread`.
IMPORTANT: If init_context is specified and the default temporary allocator is used, the thread procedure needs to call runtime.default_temp_allocator_destroy() in order to free the resources associated with the temporary allocations.
create_and_start_with_poly_data
create_and_start_with_poly_data :: proc(data: T, fn: proc(data: T), init_context: Maybe(runtime.Context), priority = Thread_Priority.Normal, self_cleanup: untyped boolean = false) -> (t: ^Thread)SourceRun a procedure with one polymorphic parameter on a different thread.
This procedure runs the given procedure on another thread. The context specified by init_context will be used as the context in which fn is going to execute. The thread will have priority specified by the priority parameter.
If self_cleanup is specified, after the thread finishes the execution of the fn procedure, the resources associated with the thread are going to be automatically freed.
Do not dereference the ^Thread pointer, if this flag is specified. That includes calling join, which needs to dereference ^Thread`.
IMPORTANT: If init_context is specified and the default temporary allocator is used, the thread procedure needs to call runtime.default_temp_allocator_destroy() in order to free the resources associated with the temporary allocations.
create_and_start_with_poly_data2
create_and_start_with_poly_data2 :: proc(
arg1: T1,
arg2: T2,
fn: proc(),
init_context: Maybe(runtime.Context),
priority: _ = Thread_Priority.Normal,
self_cleanup: untyped boolean = false,
) -> (t: ^Thread)SourceRun a procedure with two polymorphic parameters on a different thread.
This procedure runs the given procedure on another thread. The context specified by init_context will be used as the context in which fn is going to execute. The thread will have priority specified by the priority parameter.
If self_cleanup is specified, after the thread finishes the execution of the fn procedure, the resources associated with the thread are going to be automatically freed.
Do not dereference the ^Thread pointer, if this flag is specified. That includes calling join, which needs to dereference ^Thread`.
IMPORTANT: If init_context is specified and the default temporary allocator is used, the thread procedure needs to call runtime.default_temp_allocator_destroy() in order to free the resources associated with the temporary allocations.
create_and_start_with_poly_data3
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),
priority: _ = Thread_Priority.Normal,
self_cleanup: untyped boolean = false,
) -> (t: ^Thread)SourceRun a procedure with three polymorphic parameters on a different thread.
This procedure runs the given procedure on another thread. The context specified by init_context will be used as the context in which fn is going to execute. The thread will have priority specified by the priority parameter.
If self_cleanup is specified, after the thread finishes the execution of the fn procedure, the resources associated with the thread are going to be automatically freed.
Do not dereference the ^Thread pointer, if this flag is specified. That includes calling join, which needs to dereference ^Thread`.
IMPORTANT: If init_context is specified and the default temporary allocator is used, the thread procedure needs to call runtime.default_temp_allocator_destroy() in order to free the resources associated with the temporary allocations.
create_and_start_with_poly_data4
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),
priority: _ = Thread_Priority.Normal,
self_cleanup: untyped boolean = false,
) -> (t: ^Thread)SourceRun a procedure with four polymorphic parameters on a different thread.
This procedure runs the given procedure on another thread. The context specified by init_context will be used as the context in which fn is going to execute. The thread will have priority specified by the priority parameter.
If self_cleanup is specified, after the thread finishes the execution of the fn procedure, the resources associated with the thread are going to be automatically freed.
Do not dereference the ^Thread pointer, if this flag is specified. That includes calling join, which needs to dereference ^Thread`.
IMPORTANT: If init_context is specified and the default temporary allocator is used, the thread procedure needs to call runtime.default_temp_allocator_destroy() in order to free the resources associated with the temporary allocations.
destroy
destroy :: proc(thread: ^Thread)SourceWait for the thread to finish and free all data associated with it.
is_done
is_done :: proc(thread: ^Thread) -> (bool)SourceCheck if the thread has finished work.
join
join :: proc(thread: ^Thread)SourceWait for the thread to finish work.
join_multiple
join_multiple :: proc(threads)SourceWait for all threads to finish work.
pool_add_task
pool_add_task :: proc(pool: ^Pool, allocator: mem.Allocator, procedure: Task_Proc, data: rawptr, user_index: int)SourceAdd a task to the thread pool.
Tasks can be added from any thread, not just the thread that created the thread pool. You can even add tasks from inside other tasks.
Each task also needs an allocator which it either owns, or which is thread safe.
pool_destroy
pool_destroy :: proc(pool: ^Pool)Sourcepool_do_work
pool_do_work :: proc(pool: ^Pool, task: Task)SourceMostly for internal use.
pool_finish
pool_finish :: proc(pool: ^Pool)SourceProcess the rest of the tasks, also use this thread for processing, then join all the pool threads.
pool_init
pool_init :: proc(
pool: ^Pool,
allocator: mem.Allocator,
thread_count: int,
init_proc: Thread_Init_Proc,
init_data: rawptr,
fini_proc: Thread_Init_Proc,
fini_data: rawptr,
)SourceOnce initialized, the pool's memory address is not allowed to change until it is destroyed.
The thread pool requires an allocator which it either owns, or which is thread safe.
pool_is_empty
pool_is_empty :: proc(pool: ^Pool) -> (bool)SourceIf tasks are only being added from one thread, and this procedure is being called from that same thread, it will reliably tell if the thread pool is empty or not. Empty in this case means there are no tasks waiting, being processed, or _done_.
pool_join
pool_join :: proc(pool: ^Pool)SourceFinish tasks that have already started processing, then shut down all pool threads. Might leave over waiting tasks, any memory allocated for the user data of those tasks will not be freed.
pool_num_done
pool_num_done :: proc(pool: ^Pool) -> (int)SourceNumber of tasks which are done processing. Only informational, mostly for debugging. Don't rely on this value being consistent with other num_* values.
pool_num_in_processing
pool_num_in_processing :: proc(pool: ^Pool) -> (int)SourceNumber of tasks currently being processed. Only informational, mostly for debugging. Don't rely on this value being consistent with other num_* values.
pool_num_outstanding
pool_num_outstanding :: proc(pool: ^Pool) -> (int)SourceOutstanding tasks are all tasks that are not done, that is, tasks that are waiting, as well as tasks that are currently being processed. Only informational, mostly for debugging. Don't rely on this value being consistent with other num_* values.
pool_num_waiting
pool_num_waiting :: proc(pool: ^Pool) -> (int)SourceNumber of tasks waiting to be processed. Only informational, mostly for debugging. Don't rely on this value being consistent with other num_* values.
pool_pop_done
pool_pop_done :: proc(pool: ^Pool) -> (task: Task, got_task: bool)SourceUse this to take out finished tasks.
pool_pop_waiting
pool_pop_waiting :: proc(pool: ^Pool) -> (task: Task, got_task: bool)SourceMostly for internal use.
pool_shutdown
pool_shutdown :: proc(pool: ^Pool, exit_code: int)SourceForce the pool to stop all of its threads and put it into a state where it will no longer run any more tasks.
The pool must still be destroyed after this.
pool_start
pool_start :: proc(pool: ^Pool)Sourcepool_stop_all_tasks
pool_stop_all_tasks :: proc(pool: ^Pool, exit_code: int)SourceForcibly stop all running tasks.
The same notes from pool_stop_task apply here.
pool_stop_task
pool_stop_task :: proc(pool: ^Pool, user_index: int, exit_code: int) -> (bool)SourceForcibly stop a running task by its user index.
This will terminate the underlying thread. Ideally, you should use some means of communication to stop a task, as thread termination may leave resources unclaimed.
The thread will be restarted to accept new tasks.
Returns true if the task was found and terminated.
run
run :: proc(fn: proc(), init_context: Maybe(runtime.Context), priority = Thread_Priority.Normal)SourceRun a procedure on a different thread.
This procedure runs the given procedure on another thread. The context specified by init_context will be used as the context in which fn is going to execute. The thread will have priority specified by the priority parameter.
IMPORTANT: If init_context is specified and the default temporary allocator is used, the thread procedure needs to call runtime.default_temp_allocator_destroy() in order to free the resources associated with the temporary allocations.
run_with_data
run_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context), priority = Thread_Priority.Normal)SourceRun a procedure with one pointer parameter on a different thread.
This procedure runs the given procedure on another thread. The context specified by init_context will be used as the context in which fn is going to execute. The thread will have priority specified by the priority parameter.
IMPORTANT: If init_context is specified and the default temporary allocator is used, the thread procedure needs to call runtime.default_temp_allocator_destroy() in order to free the resources associated with the temporary allocations.
run_with_poly_data
run_with_poly_data :: proc(data: T, fn: proc(data: T), init_context: Maybe(runtime.Context), priority = Thread_Priority.Normal)SourceRun a procedure with one polymorphic parameter on a different thread.
This procedure runs the given procedure on another thread. The context specified by init_context will be used as the context in which fn is going to execute. The thread will have priority specified by the priority parameter.
IMPORTANT: If init_context is specified and the default temporary allocator is used, the thread procedure needs to call runtime.default_temp_allocator_destroy() in order to free the resources associated with the temporary allocations.
run_with_poly_data2
run_with_poly_data2 :: proc(arg1: T1, arg2: T2, fn: proc(), init_context: Maybe(runtime.Context), priority = Thread_Priority.Normal)SourceRun a procedure with two polymorphic parameters on a different thread.
This procedure runs the given procedure on another thread. The context specified by init_context will be used as the context in which fn is going to execute. The thread will have priority specified by the priority parameter.
IMPORTANT: If init_context is specified and the default temporary allocator is used, the thread procedure needs to call runtime.default_temp_allocator_destroy() in order to free the resources associated with the temporary allocations.
run_with_poly_data3
run_with_poly_data3 :: proc(
arg1: T1,
arg2: T2,
arg3: T3,
fn: proc(arg1: T1, arg2: T2, arg3: T3),
init_context: Maybe(runtime.Context),
priority: _ = Thread_Priority.Normal,
)SourceRun a procedure with three polymorphic parameters on a different thread.
This procedure runs the given procedure on another thread. The context specified by init_context will be used as the context in which fn is going to execute. The thread will have priority specified by the priority parameter.
IMPORTANT: If init_context is specified and the default temporary allocator is used, the thread procedure needs to call runtime.default_temp_allocator_destroy() in order to free the resources associated with the temporary allocations.
run_with_poly_data4
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),
priority: _ = Thread_Priority.Normal,
)SourceRun a procedure with four polymorphic parameters on a different thread.
This procedure runs the given procedure on another thread. The context specified by init_context will be used as the context in which fn is going to execute. The thread will have priority specified by the priority parameter.
IMPORTANT: If init_context is specified and the default temporary allocator is used, the thread procedure needs to call runtime.default_temp_allocator_destroy() in order to free the resources associated with the temporary allocations.
start
start :: proc(thread: ^Thread)SourceStart a suspended thread.
terminate
terminate :: proc(thread: ^Thread, exit_code: int)SourceForcibly terminate a running thread.
yield
yield :: proc()SourceYield the execution of the current thread to another OS thread or process.