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.
Good basic starter tutorial. thanks.
I know it is not really the scope of this post.
But I know you are interested in Ocaml and I thought that a library for parallelism in Ocaml would interests you: http://functory.lri.fr/About.html.
Thanks for the pointer. I was mostly interested in using pthreads to do a comparison of how much easier OCaml solutions are.
This is an excellent example. I plan to use this code as a good example to teach introduction to pthread and multicore programming.
I did not see any multi-core specific code in the example. The sample code seems to illustrate standard pthread API usage. Did I miss out something ?
Fully agree with what Siddhartha has pointed out. Where is code that specifies using multiple cores?
Maybe I’m missing something. Are you implying that multiple cores aren’t getting used in this code? Where is the speedup coming from then?
This is good about multithreading using pthread but can you suggest how to assign various threads in each core manually through any APIs please tell
Hi Ashish,
speedup is coming from the fact that threads get scheduled and in serial case its just function call. Try to print the cpuid. You will know. This program is just a example of pthread and it will run perfectly on uniprocessor.
Thanks for the clarification. I’ve removed mention of multiple cores from the post title and text.
what does this do:
long id = (long) t;
why is the second ‘long’ in brackets?
Very Interesting example
That’s C notation for a type cast.