Threads

This module deals with Threads. The general items covered in this module include:

  • Threads
  • Mutexes
  • Threading primitives
  • Semaphores
  • Thread Groups

Thread

Here is the struct definition for the Thread primitive:

Thread :: struct {
    index : Thread_Index;
    proc  : Thread_Proc;
    data  : *void;
    workspace : Workspace;
    starting_context: Context;
    starting_temporary_storage: Temporary_Storage;
    allocator_used_for_temporary_storage: Allocator;
    worker_info: *Thread_Group.Worker_Info; // Used by Thread_Group; unused otherwise.
    #if _STACK_TRACE  stack_trace_sentinel: Stack_Trace_Node;
    using specific : Thread_Os_Specific;
}

The Thread_Os_Specific is information for specific OS’es. The index starts at zero, and is incremented every time a new thread is spawned with thread_init.

thread_create :: (proc: Thread_Proc, temporary_storage_size : s32 = 16384, starting_storage: *Temporary_Storage = null) -> *Thread;

This function creates a thread using heap allocation.

thread_init :: (thread: *Thread, proc: Thread_Proc, temporary_storage_size : s32 = 16384, starting_storage: *Temporary_Storage = null) -> bool {

This function initializes a thread. This function does not start a thread, but rather just initializes data.

thread_start :: (thread: *Thread);

This function starts the thread.

Thread Group

A Thread_Group is a way of launching a bunch of threads to asynchronously respond to requests. You can initialize a thread group using the init :: () function. Call start :: () on the Thread_Group to start running the threads. When you want the threads to stop running, call shutdown :: (). It is best practice to call shutdown before your program exits.

The Thread_Group specializes in calling only one function. It does not compute any arbitrary amount of work passed to it, and only deals with one specific function.

init :: (group: *Thread_Group, num_threads: s32, group_proc: Thread_Group_Proc, enable_work_stealing := false);

This function initializes the Thread_Group. Changing the number of threads increases the number of threads in the Thread_Group.

The Thread_Group_Proc function pointer is defined as follows:

Thread_Group_Proc :: #type (group: *Thread_Group, thread: *Thread, work: *void) -> Thread_Continue_Status;

The group is the Thread_Group, the thread refers to the particular thread in question, and the work is the work passed into the Thread_Group through the add_work :: () function. The Thread_Continue_Status is returned by procedure. Returning .STOP causes the thread to terminate. .CONTINUE causes the thread to continue to run. You usually want to return .CONTINUE, and .STOP is for a resource shortage of some kind.

start :: (group: *Thread_Group);

Starts up the threads in the thread group.

add_work :: (group: *Thread_Group, work: *void, logging_name := "");

Adds a unit of work, which will be given to one of the threads.

Basic Thread Group Example

main :: () {
  thread_group: Thread_Group;
  init(*thread_group, 4, thread_test, true);
  thread_group.logging = false; // turns debugging off. set logging = true to turn on debugging

  start(*thread_group);
  for i: 0..10
    add_work(*thread_group, null);

  sleep_milliseconds(5000);

  shutdown(*thread_group);
  print("exit program\n");
}

thread_test :: (group: *Thread_Group, thread: *Thread, work: *void) -> Thread_Continue_Status {
  print("thread_test :: () from thread.index = %\n", thread.index);
  return .CONTINUE;
}

#import "Thread";
#import "Basic";

In this basic thread group example, we initialize a thread group, start it, add work to the group, and shutdown the thread group.

Thread Group Example with response to completed work

In the following example, we kick off a set of threads to do a set of tasks, then we use get_completed_work :: () to get the results back. In the main thread, we do something with those results achieved.

main :: () {
  thread_group: Thread_Group;
  init(*thread_group, 4, thread_test, true);
  thread_group.logging = false;

  start(*thread_group);
  arr: [10] Work;
  for i: 0..9 {
    arr[i].count = 10000;
    add_work(*thread_group, *arr[i]);
  }

  sleep_milliseconds(5000);

  work_list := get_completed_work(*thread_group);
  total := 0;
  for work: work_list {
    val := cast(*Work) work;
    print("%\n", val.result);
    total += val.result;
  }
  print("Total = %\n", total);
  shutdown(*thread_group);
  print("exit program\n");
}

thread_test :: (group: *Thread_Group, thread: *Thread, work: *void) -> Thread_Continue_Status {
  w := cast(*Work)work;
  print("thread_test :: () from thread.index = %, work.count = %\n", thread.index, w.count);

  sum := 0;
  for i: 0..w.count {
    sum += i;
  }
  // return the result.
  w.result = sum;
  return .CONTINUE;
}

Work :: struct {
  count: int;
  result: int;
}


#import "Thread";
#import "Basic";
1 Like