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}

Declarations Used 13