changelog:
- 6 Apr 2026: fix bug in
fail_test()call inpool-test.c - 8 Apr 2026: add note about ThreadSanitizer
- 8 Apr 2026: fix test cases which were too picky about task numbering
in
pool-test.c - 8 Apr 2026: add pool-example.c
- 9 Apr 2026: fix an additional test case which was too picky about
task numbering in
pool-test.c - 9 Apr 2026: reorganize section on testing; add some more advice on testing
- 10 Apr 2026: make pool-test.c wait longer for tests that
pause
; cleanup grammar in with section on testing; note that pool-test.c is bad at testing waiting - 11 Apr 2026: add some test cases to pool-test.c to hopefully notice broken pool_wait()s more often
- 14 Apr 2026: adjust
two thread, four tasks, one submitting extra task, tasks 0/1, 4/5 using barriers
in pool-test.c not to be too picky about ordering - 15 Apr 2026: remove redundant
and all tasks are waited for
from description of pool_stop - 17 Apr 2026: fix incorrect #ifdef to #ifndef in header guard
- 26 Apr 2026: update pool-test.c to avoid calling pool_stop before all tasks are submitted in some cases
1 Your Task
Implement a
thread pool
library with the following functions:void pool_setup(int threads);Create exactly
threadsthreads, each of which will later run tasks submitted bypool_submit_task.You may assume that this is called before any of the below functions are called.
int pool_submit_task(task_fn task, void *argument);Record a task as submitted so it will later be run by one of the threads created by the thread pool, then return.
Tasks must be started in the order they are submitted.
When the task is eventually run from one of the threads created by
pool_setup, call the function specified by the function pointertaskwith the single argumentargument. Then record the return value for later retrieval withpool_get_task_result.This function must return an identifier that represents this instance of the task. This identifier can be used to retrieve the return value (as long as
pool_setupis not called again).This function must be safe to call from a task.
void pool_wait();Wait for all submitted tasks up to this point to complete running, if they haven’t already.
You may assume this will not be called from a task.
If new tasks are submitted while
pool_wait()is running, you may either:- let those tasks run normally (possibly only finishing after
pool_waitreturns); or - delay those tasks until after
pool_wait()returns
- let those tasks run normally (possibly only finishing after
void *pool_get_task_result(int task_id);Retrieve the return value from the task with the given
task_id.Return
NULLif the task as not finished or does not exist.We do not care what this function does if there has been a new call to
pool_setupsince the task returned.void pool_stop();Cause all threads in the thread pool to finish processing currently submitted tasks and then terminate. Wait for all the threads to be joined.
Note that the task return values may still be retrieved after
pool_stopreturns.After
pool_stop()returns, it should be possible to callpool_setup()again (possibly with a different number of threads) and then submit and wait functions normally again.We do not care how this function behaves if new tasks are submitted after it is started but before a subsequent call to
pool_setup(). We also do not care how this function behaves if there is a pending call topool_wait()in another thread.
Your implementation does not need to support more than 200 tasks. You may assume no more than 200 tasks will be submitted.
Whenever a thread needs to wait for an event (such as one of the threads started by
pool_setupwaiting for a new task), it may not consume a lot of compute time (busy wait
). The most likely way you’d do this is by having the thread call a synchronization function that will waits until the event has likely happened.Test your implementation.
To help you with this, we supply:
- this pool-example.c, a relatively small example you can use as a base for writing tests and for manual testing and debugging, and
- this pool-test.c [last updated 2026-04-26], which tries a bunch of patterns for running tasks
I would recommend modifying pool-example.c to have the tasks take longer (such as by having them call
nanosleep) and then use that check that yourpool_waitandpool_stopactually wait for the tasks to finish. (pool-test.cmight not reliably detect this type of problem.)In addition, I would strongly recommend compiling and linking with
-fsanitize=threadas part of your testing to enable ThreadSanitizer, which can help identify race conditions more reliably. We will do this as part of our grading.Note that, because race conditions depend on timing, tests may not consistently trigger those bugs. In addition to using ThreadSanitizer, you can try to trigger race conditions more consistently by trying a test that might have a race condition many times (for example, in a loop).
Also, note that these examples/tests don’t check that your programs do not consume a lot of compute time while waiting for an event (and we intend to check that when grading).
Submit your implementation using the submission site.
2 pool.h
#ifndef POOL_H_
#define POOL_H_
// The function signature for a task.
//
// A task should be a function like:
//
// void *my_task(void*) { ... }
//
typedef void*(*task_fn)(void*);
// Initialize the thread pool.
void pool_setup(int threads);
// Submit a new task to the thread pool and return its ID.
int pool_submit_task(task_fn task, void *argument);
// Get the return value from a task.
void *pool_get_task_result(int task_id);
// Return after all existing tasks have finished.
void pool_wait(void);
// Tell the thread pool threads to finish any current task and then exit.
void pool_stop(void);
#endif3 Example of pool library usage
Suppose a user wanted to use the thread pool library to handle summing arrays of numbers. They might make a function to do this like:
struct SumInfo {
int *array;
int array_size;
};
void *sum_function(void *argument) {
SumInfo *sum_info = argument;
int sum = 0;
for (int i = 0; i < sum_info->array_size; i += 1) {
sum += sum_info->array[i];
}
return (void*) sum;
}
Then arrange for many sums to be computed by the thread pool:
pool_setup(num_threads);
struct SumInfo sum_A_info = { .array = arrayA, .array_size = ARRAY_SIZE };
int task_A = pool_submit_task(sum_function, &sum_A_info);
struct SumInfo sum_B_info = { .array = arrayB, .array_size = ARRAY_SIZE };
int task_B = pool_submit_task(sum_function, &sum_B_info);
...
pool_wait();
int sum_A = (int) pool_get_task_result(task_A)
int sum_B = (int) pool_get_task_result(task_B)
...
pool_stop();
The thread pool will start exactly num_threads threads,
regardless of how many arrays there are to sum. Then it will have those
threads run array-summing operations until there are no operations
left.