Here’s a simple C program that demonstrates the use of the pthreads library. The following code can be downloaded here, so you can test it yourself. Simply compile with the command:
gcc -lpthread pthreads_test.c -o pthreads_test
First, let’s import some necessary headers, mainly pthread.h which provides the POSIX threads implementation.
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
Now, define a task that takes some non-negligible amount of time to complete. We pass in an id simply to identify each call to the task in output messages.
void *task(int id) {
printf("Task %d started\n", id);
int i;
double result = 0.0;
for (i = 0; i < 1000000; i++) {
result = result + sin(i) * tan(i);
}
printf("Task %d completed with result %e\n", id, result);
}
We can run the above task a desired number of times in serial by calling the following function:
void *serial(int num_tasks) {
int i;
for (i = 0; i < num_tasks; i++) {
task(i);
}
}
Now, let’s define task in a manner suitable for being called in its own thread. All this requires is to use pthread_exit to let the parent process know this thread has completed. Actually this doesn’t affect the simple program we’re writing, but it’s the correct thing to do if you want to later join threads. Also, we have to make sure the function signature matches the requirements of pthread_create.
void *threaded_task(void *t) {
long id = (long) t;
printf("Thread %ld started\n", id);
task(id);
printf("Thread %ld done\n", id);
pthread_exit(0);
}
Finally, the interesting code generates a new thread for each call to threaded_task.
void *parallel(int num_tasks)
{
int num_threads = num_tasks;
pthread_t thread[num_threads];
int rc;
long t;
for (t = 0; t < num_threads; t++) {
printf("Creating thread %ld\n", t);
rc = pthread_create(&thread[t], NULL, threaded_task, (void *)t);
if (rc) {
printf("ERROR: return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
}
The main function runs a specified number of tasks either in serial or parallel.
void *print_usage(int argc, char *argv[]) {
printf("Usage: %s serial|parallel num_tasks\n", argv[0]);
exit(1);
}
int main(int argc, char *argv[]) {
if (argc != 3) {print_usage(argc, argv);}
int num_tasks = atoi(argv[2]);
if (!strcmp(argv[1], "serial")) {
serial(num_tasks);
} else if (!strcmp(argv[1], "parallel")) {
parallel(num_tasks);
}
else {
print_usage(argc, argv);
}
printf("Main completed\n");
pthread_exit(NULL);
}
That’s it! Now, compile the program using the command given at the beginning of this post. Here are some results on a MacBook Air.
$ time ./pthreads_test serial 4
Task 0 started
Task 0 completed with result -3.153838e+06
Task 1 started
Task 1 completed with result -3.153838e+06
Task 2 started
Task 2 completed with result -3.153838e+06
Task 3 started
Task 3 completed with result -3.153838e+06
Main completed
real 0m0.673s
user 0m0.665s
sys 0m0.003s
$ time ./pthreads_test parallel 4
Creating thread 0
Creating thread 1
Creating thread 2
Creating thread 3
Main completed
Thread 1 started
Task 1 started
Thread 2 started
Task 2 started
Thread 3 started
Task 3 started
Thread 0 started
Task 0 started
Task 3 completed with result -3.153838e+06
Thread 3 done
Task 2 completed with result -3.153838e+06
Task 1 completed with result -3.153838e+06
Thread 2 done
Thread 1 done
Task 0 completed with result -3.153838e+06
Thread 0 done
real 0m0.378s
user 0m0.667s
sys 0m0.007s
Note that threads do not necessarily start or end in order. And the point of it all; there is a factor 2x speedup for the multi-threaded run! Here’s a plot of the real time against number of tasks for 2, 4, 8, 16, 32, and 64 tasks.
You can learn more by following the excellent tutorial provided here, from which the above example was derived.