Complete working example (compile with cc pthread_example.c -lpthread)

#include <stdio.h>
#include <pthread.h>

typedef struct thread_data {
  int counter;
  int thread_num;
} thread_data_t;


void *thread_main(void *arg)
{
    thread_data_t *td = (thread_data_t*)arg;
    int increment = td->thread_num + 10;
    printf("starting thread nr %d and incrementing it's counter by %d \n", td->thread_num, increment);
    td->counter += increment;
}

#define NUM_THREADS 4

pthread_t threads[NUM_THREADS];
thread_data_t td[NUM_THREADS];

int main(int argc, char *argv[]) {
    for (size_t i = 0; i < NUM_THREADS; i++) {
        td[i].thread_num = i;
        int retval = pthread_create(&threads[i], NULL, thread_main, &td[i]);
        if (retval != 0) {
            printf("failed when creating thread\n");
        }
    }
    for (size_t i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
        printf("thread nr %d closing with it's counter at %d \n", td[i].thread_num, td[i].counter);
    }
}

So what’s happening here? Explanation:

This line:

pthread_t threads[NUM_THREADS]

Simply creates an array of empty pthread_t structs that are then passed to and filled in by the pthread_create function

The line below it:

thread_data_t td[NUM_THREADS];

Creates our thread data structs where we can store thread related information. This is something created by the user, you can pass anything to the pthread_create function since it is a void *

In the first loop the pthread_create call is issued for each thread we wan’t to create. Let’s examine the call to pthread_create:

pthread_create(&thread[i], NULL, thread_main, &td[i]);

We are passing the address of our pthread_t thread structs (&thread[i]), we are passing NULL as the second argument which is used for passing a pthread_attr_t struct containing attributes for the thread we are creating - NULL means we are happy with the defaults. We are also passing our thread_main function which is a function that must return a void * and take a void * as argument. The void * that it takes as an argument is passed to pthread_create as the last argument - in our example it is the address of the &td[i] struct.

After each pthread_create call we check the return value(retval) - which should be 0 on successful thread creation.

Now all are threads are successfully started. And the main “parent” thread would happily exit without this for loop containing:

pthread_join(threads[i], NULL);

This call makes sure that we wait for all our threads to complete before terminating the program. The first argument is our thread to wait for(thread[i]) and the second argument can be used to get the exit status of the thread. We just pass NULL in this example, but if we wanted to get the exit status we could pass a void * and the join function would copy the value passed to pthread_exit inside the thread to that void *.