POSIX threads (Pthreads) is a library for writing multi-threaded programs. It can be used on POSIX systems (Unix, Linux, OSX), but also Windows.
It is a C library, but can be used with C++ as well.
The following program illustrates the very basics of Pthreads:
#include <pthread.h>
#include <stdio.h>
#define THREADS 4
/* the function called for each thread */
void* f(void* data) {
printf("Hello from a thread!\n");
pthread_exit(NULL);
}
int main() {
/* an array of threads */
pthread_t threads[THREADS];
/* start all of the threads */
for (int t = 0; t < THREADS; t++) {
pthread_create(&threads[t], NULL, f, NULL);
}
pthread_exit(NULL);
}
pthread_create takes 4 parameters:
Pthreads can be compiled with gcc or clang, as long as we pass the "-pthread" option:
$ gcc hello.c -pthread $ ./a.out
Without the pthread option, we will get linker errors.
Pthreads can be used with C++ programs as well:
#include <pthread.h>
#include <iostream>
const int THREADS = 4;
/* the function called for each thread */
void* f(void* data) {
std::cout << "Hello from a thread!" << std::endl;
pthread_exit(NULL);
}
int main() {
/* an array of threads */
pthread_t threads[THREADS];
for (int t = 0; t < THREADS; t++) {
pthread_create(&threads[t], NULL, f, NULL);
}
pthread_exit(NULL);
}
Compile as above, except with g++ or clang++.
To pass data to your thread, you need to pass it as a void* to the last parameter of pthread_create.
The example below shows how this can be done:
#include <pthread.h>
#include <stdio.h>
#define THREADS 4
void* f(void* idp) {
int id = * (int*) idp;
printf("Thread %d checking in!\n", id);
pthread_exit(NULL);
}
int main() {
pthread_t threads[THREADS];
int ids[THREADS];
for (int i = 0; i < THREADS; i++) {
ids[i] = i;
pthread_create(&threads[i], NULL, f, &ids[i]);
}
pthread_exit(NULL);
}
Here we pass a pointer to an int and then cast it as a void*. This is done so that any type of data can be passed into a thread.
You must also ensure that each thread's input parameters are kept safe. For instance, if we tried to reuse one int for all of the threads, it would not work properly:
#include <pthread.h>
#include <stdio.h>
#define THREADS 4
void* f(void* idp) {
int id = * (int*) idp;
printf("Thread %d checking in!\n", id);
pthread_exit(NULL);
}
int main() {
pthread_t threads[THREADS];
/* now they all share an id -- this will not work well */
int id;
for (int i = 0; i < THREADS; i++) {
id = i;
pthread_create(&threads[i], NULL, f, &id);
}
pthread_exit(NULL);
}
The main function does not automatically wait for the threads it spawns to finish:
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#define THREADS 4
/* the function called for each thread */
void* f(void* idp) {
int id = * (int*) idp;
sleep(1);
printf("Thread %d checking in!\n", id);
pthread_exit(NULL);
}
int main() {
/* an array of threads */
pthread_t threads[THREADS];
int ids[THREADS];
for (int i = 0; i < THREADS; i++) {
ids[i] = i;
pthread_create(&threads[i], NULL, f, &ids[i]);
}
printf("All threads finished!\n");
return 0;
}
(If we have the main function call pthread_exit(NULL) instead of return 0, then the threads will at least get a chance to finish).
Most times however, we will have points where we want to wait for all worker threads to finish. This can be done by joining the thread with the pthread_join function:
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#define THREADS 4
/* the function called for each thread */
void* f(void* idp) {
int id = * (int*) idp;
sleep(1);
printf("Thread %d checking in!\n", id);
pthread_exit(NULL);
}
int main() {
/* an array of threads */
pthread_t threads[THREADS];
int ids[THREADS];
/* spawn the threads */
for (int i = 0; i < THREADS; i++) {
ids[i] = i;
pthread_create(&threads[i], NULL, f, &ids[i]);
}
/* wait for the threads to finish */
for (i = 0; i < THREADS; i++) {
pthread_join(threads[i], NULL);
}
printf("All threads finished!\n");
pthread_exit(NULL);
}
Pthreads also uses void* types to return data from a thread. An issue with this is that we cannot return a local variable. What is wrong with this:
void* f(void* idp) {
int answer;
/* do calculation ... */
return (void*) &answer;
}
In order to get around this, we can use malloc/new to allocate memory for each thread:
void* f(void* idp) {
int* answer = malloc(sizeof(int));
/* do calculation ... */
return (void*) answer;
}
But then we must free/delete the memory in the main function!
The last parameter to pthread_join is a pointer in which to place the return value:
int* result;
pthread_join(threads[i], (void**) &result);
free(result);
Copyright © 2024 Ian Finlayson | Licensed under a Creative Commons BY-NC-SA 4.0 License.