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:
Creating multiple threads using the
thrd_createfunction.Safely accessing and modifying a shared resource across threads using an atomic data type (
atomic_int) and thread-safe manipulation via theatomic_fetch_addmacro.Joining threads using the
thrd_joinfunction.Simulating time-consuming operations within threads using the
thrd_sleepfunction.
Alternatively, POSIX multithreading via the pthread.h header can be used, though it is not part of the C standard library.
Implementation Notes
The shared resource stored in the
m_workvariable is of typeatomic_int. This type guarantees atomic read and write (assignment) operations.Incrementing the shared resource must not be done using
m_work++(m_work = m_work + 1). This syntax does not guarantee that reading and writing occur in a single atomic step (another thread could access the variable before the current thread finishes both actions). Theatomic_fetch_add(&m_work, 1)call increments the variable by 1 as an atomic operation and returns its previous value.The value of the shared resource must be retrieved using the
atomic_loadmacro instead of accessing it directly.The duration of the random sleep (in seconds) is generated in a thread-safe manner using the
rand_rPOSIX function.The
printffunction is thread-safe and can be called from multiple threads. However, there is no guarantee that output from different threads will appear in the expected order.
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;
}