Multithreading in C with threads.h

Demonstration of multithreading in C11 using threads.h for thread creation and stdatomic.h for safe, atomic resource sharing.

Contents

Introduction

The C standard library offers multithreading via the threads.h header (C11). Atomic types, such as atomic_int, support atomic read and write operations and are defined in the stdatomic.h header (C11).

The example program demonstrates the following tasks:

Alternatively, POSIX multithreading via the pthread.h header can be used, though it is not part of the C standard library.

Implementation Notes

Source Code


// Enable `rand_r`.
#define _XOPEN_SOURCE 500
#define _POSIX_C_SOURCE 199506L

#include <stdatomic.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <threads.h>
#include <time.h>

#define NUM_THREADS     4
#define NUM_ITERATIONS  5

// Resource shared between the threads.
static atomic_int m_work = 0;

// Thread worker function matching signature: `int (*)(void *)`.
static int do_work(void *tn);

int main(void)
{
    thrd_t threads[NUM_THREADS];

    // Create threads.
    for (int t = 0; t < NUM_THREADS; t++)
    {

        // Safe pointer casting using `intptr_t` ensures portability.
        if (
            thrd_create(&threads[t], do_work, (void *)(intptr_t)t) == thrd_error
        )
        {
            printf("Error creating thread #%d.\n", t);
            return EXIT_FAILURE;
        }
    }

    // Wait for all threads to finish.
    for (int t = 0; t < NUM_THREADS; t++)
    {
        int res;
        if (thrd_join(threads[t], &res) == thrd_error)
        {
            printf("Error joining thread #%d.\n", t);
        }
    }
    printf("All threads finished.\n");

    // Safely load the final atomic value.
    int final_work = atomic_load(&m_work);

    printf("Work result is %d.\n", final_work);
    return EXIT_SUCCESS;
}

static int do_work(void *tn)
{
    int t = (int)(intptr_t)tn;

    // Seed a thread-local random state to make it thread-safe.
    unsigned int seed = (unsigned int)time(NULL) ^ (unsigned int)t;

    printf("Thread #%d started.\n", t);

    for (int i = 0; i < NUM_ITERATIONS; i++)
    {

        // Sleep for 0 to 5 seconds to simulate blocking work.
        struct timespec requested_time = { 
            .tv_sec = rand_r(&seed) % 5, .tv_nsec = 0
        };
        printf(
            "Thread #%d sleeping for %ld seconds.\n", t, requested_time.tv_sec
        );
        thrd_sleep(&requested_time, NULL);

        // `atomic_fetch_add` returns the _previous_ value. 
        // Add 1 to show the actual updated value.
        int old_work = atomic_fetch_add(&m_work, 1);
        printf("Thread #%d incremented work to %d.\n", t, old_work + 1);
    }

    printf("Thread #%d exiting successfully.\n", t);
    return thrd_success;
}