2 - Passing and Returning values from Threads
Practice Program
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <math.h>
#define NUM_THREADS 4
void* worker(void* arg) {
printf("Thread %d: Executing\n", *(int*)arg);
const int thread_id = *(int*)arg;
void* result = calloc(1, sizeof(double));
for (int i = 0; i < 1000000; i++) {
*(double*)result += sin(thread_id) + tan(thread_id);
}
printf("Thread %d: Completed\n", thread_id);
free(arg);
pthread_exit(result);
}
int main() {
pthread_t thread[NUM_THREADS];
void* thread_result[NUM_THREADS];
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
for (int i = 0; i < NUM_THREADS; i++) {
printf("Thread %d: Creating\n", i);
void* thread_arg = malloc(sizeof(int));
*(int*)thread_arg = i;
const int creation_status = pthread_create(&thread[i], &attr, worker, thread_arg);
if (creation_status) {
printf("Thread %d: Unable to create thread\n", i);
}
}
pthread_attr_destroy(&attr);
for (int i = 0; i < NUM_THREADS; i++) {
const int join_status = pthread_join(thread[i], &thread_result[i]);
if (join_status) {
printf("Thread %d: Unable to join thread\n", i);
}
}
printf("Results from threads:-\n");
for (int i = 0; i < NUM_THREADS; i++) {
printf("Thread %d: Result - %f\n", i, *(double*)thread_result[i]);
free(thread_result[i]);
}
return 0;
}1. Program Overview
The program creates four worker threads that perform a CPU-bound computation and return the result to the main thread.
Each thread:
- Receives a thread identifier
- Performs a mathematical computation
- Stores the result in dynamically allocated memory
- Returns the result to the main thread
The main thread:
- Creates worker threads
- Waits for them to finish using
pthread_join - Collects their results
- Frees the allocated memory
2. Defining the Number of Threads
#define NUM_THREADS 4This constant determines:
- Number of worker threads created
- Size of thread tracking arrays
pthread_t thread[NUM_THREADS];
void* thread_result[NUM_THREADS];thread[]stores thread identifiersthread_result[]stores returned values from threads
3. Worker Thread Function
void* result = calloc(1, sizeof(double));The worker thread function allocs space to store the result from the computation.
Why is the allocation needed one might ask. Every thread has it's separate stack and the local variables are items of the stack. When the functions exits, all the resources are freed, including the stack.
In order to avoid the loss of results, we make use of Heap memory which lives beyond a thread's lifecycle. Otherwise, we would end up with a dangling pointer.
Data returned by threads must live on the heap, not the stack.
The other thing to note here is the cleanup of argument memory. Arguments of the threads here are dynamically allocated and needs to be freed to avoid the memory leaks.
free(arg);Lastly, the results are returned by using
pthread_exit(result);pthread_exit terminates the thread and passes a pointer back to the joining thread.
Equivalent alternative:
return result;4. Thread Attributes
Thread attributes allow configuring thread behavior.
pthread_attr_t attr;Initialize attributes
pthread_attr_init(&attr);Set detach state
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);This explicitly sets threads to joinable.
Although many implementations default to joinable threads, explicitly setting this improves portability and clarity.
5. Creating Threads
Threads are created inside a loop.
void* thread_arg = malloc(sizeof(int));
*(int*)thread_arg = i;A separate argument is allocated for each thread.
This is important because passing the address of i directly would create a race condition, where all threads read the same variable as it changes.
Thread creation:
pthread_create(&thread[i], &attr, worker, thread_arg);Parameters:
- Thread identifier storage
- Thread attributes
- Worker function
- Argument passed to the thread
6. Destroying Thread Attributes
Once all threads are created, attributes are no longer needed.
pthread_attr_destroy(&attr);This releases resources used by the attribute object.
7. Waiting for Threads to Finish
The main thread waits for each worker thread.
pthread_join(thread[i], &thread_result[i]);pthread_join performs two tasks:
- Blocks until the thread terminates
- Retrieves the value returned by the thread
The returned value is stored in:
thread_result[i]Each entry is a pointer to the result computed by a thread.