Multithreading in C with threads.h
The C standard library offers multithreading via the header threads.h
(C11). Atomic types, such as atomic_int
, supporting atomic read and write operations are defined in the header stdatomic.h
(C11).
The example program demonstrates the following tasks:
Creating multiple threads using the function
thrd_create
.Safely accessing and manipulating a resource that is shared between the threads by using an appropriate atomic data type (
atomic_int
in this example) as well as thread-safe data manipulation via the macroatomic_fetch_add
.Joining threads using the function
thrd_join
.Simulating time-consuming operations in threads using the function
thrd_sleep
.
Alternatively, POSIX multithreading offered via the header pthread.h
, which is not part of the C standard library, can be used.
Implementation Notes
The shared resource stored in the variable
m_work
is of typeatomic_int
. This type guarantees atomic read and write (assignment) operations for the variable.Incrementing the shared resource must not be done using
m_work++
(m_work = m_work + 1
) as there is no guarantee that reading and writing are performed in a single atomic step (no other thread must read or write the variable before the current thread has finished both reading its value and writing its new value). The callatomic_fetch_add(&m_work, 1)
increments the variable by 1 as an atomic operation and returns the variable’s new value.The function
printf
is thread-safe and can therefore be called from multiple threads. However, there is not guarantee that calls to this function from different threads are executed in the correct order.
Source Code
#include <stdatomic.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;
// Thread worker function. For simplicity, we pass the thread number directly
// as the pointer in the `tn` parameter.
static void *do_work(void *tn);
int main(void)
{
thrd_t threads[NUM_THREADS];
// Create threads.
for (int t = 0; t < NUM_THREADS; t++)
{
if (
thrd_create(
&threads[t],
(thrd_start_t)do_work,
(void *)(long)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++)
if (thrd_join(threads[t], NULL) == thrd_error)
printf("Error joining thread #%d.\n", t);
printf("All threads finished.\n");
printf("Work result is %d.\n", m_work);
return EXIT_SUCCESS;
}
static void *do_work(void *tn)
{
int t = (int)(long)tn;
printf("Thread #%d created (thread ID = %lu).\n", t, thrd_current());
for (int i = 0; i < NUM_ITERATIONS; i++)
{
// Let thread sleep for 0 to 5 seconds to simulate work. Actual sleep
// may be shorter than the requested number of seconds, however, this
// is irrelevant for this program.
struct timespec requested_time = { .tv_sec = rand() % 5 };
printf(
"Thread #%d sleeping for %ld seconds.\n",
t,
requested_time.tv_sec
);
thrd_sleep(&requested_time, NULL);
// Increment the resource shared between the threads by 1.
int work = atomic_fetch_add(&m_work, 1);
printf("Thread #%d incremented work to %d.\n", t, work);
}
printf("Thread #%d exiting successfully.\n", t);
thrd_exit(EXIT_SUCCESS);
}