thread/pool/basic
Code
basic.odin ¶132 linesSource
1package main
2
3import "base:runtime"
4import "core:fmt"
5import "core:math/rand"
6import "core:mem"
7import "core:thread"
8
9// The number of threads in the pool.
10THREAD_COUNT :: 8
11
12// The number of tasks we want to perform.
13// These tasks will be distributed among threads of the pool.
14TASK_COUNT :: 64
15
16main :: proc() {
17 // Declare a variable for the thread pool.
18 // The pool is not initialized and no threads are running.
19 pool: thread.Pool
20
21 // The thread pool requires an allocator which it either owns,
22 // or which is thread safe.
23 pool_allocator: mem.Allocator
24
25 // For simplicity's sake, we use the default context allocator.
26 // We can do it because in this example we will not allocate
27 // anything after the pool is initialized.
28 pool_allocator = context.allocator
29
30 // Here we initialize the thread pool.
31 // We provide an allocator and tell how many threads in the pool we need.
32 thread.pool_init(&pool, pool_allocator, THREAD_COUNT)
33 // After this point, it's not allowed to change the pool's memory address.
34
35 // Now we start the pool, which internally starts all the threads.
36 thread.pool_start(&pool)
37 // After this point, it's not allowed to access pool's members directly,
38 // since it might lead to VERY nasty bugs.
39 //
40 // Instead, we should interact with the pool via `thread.pool_...` procedures
41 // (`thread.pool_add_task`, `thread.pool_num_done`, `thread.pool_is_empty`, etc.).
42
43
44 // Defer the pool destruction at the end of the current scope.
45 // This ensures that the pool will be properly destroyed at the end,
46 // and used resources will be freed.
47 defer thread.pool_destroy(&pool)
48
49
50 // Usually a task takes some input data and outputs some results.
51 // In order to do so we have to create a chunk of memory that persists
52 // for the whole duration of a task lifetime:
53 // from the moment when it's added, while it waits to be performed,
54 // when it's done, until you get and process the result.
55 //
56 // For simplicity, we just create an array that contains space
57 // for all tasks we plan to perform in this example.
58 task_data_array: [TASK_COUNT]Add_Task_Data
59 // NOTE: This memory is created on the stack, which does not violate
60 // the allocation limitation described above.
61
62 // Here is a loop where we add tasks to the pool.
63 for task_index in 0..<TASK_COUNT {
64 // A task also requires an allocator which it either owns,
65 // or which is thread safe.
66 task_allocator: mem.Allocator
67
68 // However, the allocator is necessary only if you need to allocate memory
69 // inside the task procedure. Since we know for a fact that our task
70 // does not allocate memory, we can use `nil_allocator`.
71 //
72 // Use of `nil_allocator` also protects you in case of accidental allocation.
73 // Instead of a nasty memory bug, you'll get an allocator error.
74 task_allocator = runtime.nil_allocator()
75
76 // Here we select a "chunk" from our data array for the task we create.
77 task_data := &task_data_array[task_index]
78
79 // We initialize the input data for the task with random integers
80 // in the range from 0 to 99 (max value is exclusive).
81 task_data^ = {
82 in_number_a = int(rand.int31_max(100)),
83 in_number_b = int(rand.int31_max(100)),
84 }
85
86 // Now we finally add a new task to the pool. Besides the allocator,
87 // the task creation requires a procedure that will be executed in another thread,
88 // alongside a pointer to the task data that will be passed to that procedure;
89 // the last argument is `user_index` which is basically a task ID.
90 thread.pool_add_task(&pool, task_allocator, add_task_handler, task_data, task_index)
91 }
92
93 fmt.println("Wait for all tasks to finish...")
94 // We call `pool_finish` to stop the execution of the main thread and wait
95 // for all tasks to be done. Actually, the main thread is not just waiting;
96 // it also completes tasks, which effectively increases the pool threads by 1.
97 thread.pool_finish(&pool)
98
99 fmt.println("Preview results of the first three tasks:")
100 for _ in 0..<3 {
101 // Here we take one complete task from the pool.
102 // Tasks are taken in order of completion:
103 // pool_pop_done() -> a task finished first
104 // pool_pop_done() -> a task finished second
105 task, _ := thread.pool_pop_done(&pool)
106 // NOTE: We ignore the second return value since we already know all tasks are done.
107
108 // Cast the task data to the type we expected.
109 data := cast(^Add_Task_Data)task.data
110 // And print the result.
111 fmt.printfln(" %v + %v = %v", data.in_number_a, data.in_number_b, data.out_results)
112 }
113}
114
115// This struct contains input data for the task
116// and a place to store the result.
117Add_Task_Data :: struct {
118 in_number_a: int,
119 in_number_b: int,
120 out_results: int,
121}
122
123// This procedure handles the add task.
124// It expects two numbers and outputs their sum.
125add_task_handler :: proc(task: thread.Task) {
126 // The data is passed as a `rawptr`,
127 // so we cast it to the type expected by the task.
128 data := cast(^Add_Task_Data)task.data
129
130 // Compute and store the sum.
131 data.out_results = data.in_number_a + data.in_number_b
132}