Skip to content
  • Threads & Atomics (Intro) · C

Threads & Atomics (Intro)

LearnViaNotes @learnvianotes· 2 min read

C11 standardised a memory model, <stdatomic.h> and an optional <threads.h>. In practice most C code uses POSIX threads (pthread). Shared data needs a mutex or atomics — otherwise it is a data race (undefined behaviour).

Creating and joining threads

C — pthreads (Linux: compile with -pthread)
#include <stdio.h>
#include <pthread.h>

typedef struct { int id; long from, to, result; } Job;

void *sum_range(void *arg) {                 /* thread entry: void *(*)(void *) */
    Job *job = arg;
    long s = 0;
    for (long i = job->from; i < job->to; i++) s += i;
    job->result = s;                          /* each thread writes only its own Job */
    return NULL;
}

int main(void) {
    enum { N = 4 };
    pthread_t tid[N];
    Job jobs[N];
    for (int i = 0; i < N; i++) {
        jobs[i] = (Job){i, i * 250000L, (i + 1) * 250000L, 0};
        pthread_create(&tid[i], NULL, sum_range, &jobs[i]);
    }
    long total = 0;
    for (int i = 0; i < N; i++) {
        pthread_join(tid[i], NULL);           /* wait, then read the result */
        total += jobs[i].result;
    }
    printf("sum 0..999999 = %ld\n", total);
    return 0;
}
Output
sum 0..999999 = 499999500000

Protecting shared state

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

long plain_counter = 0;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
atomic_long atomic_counter = 0;

void *work(void *arg) {
    (void)arg;
    for (int i = 0; i < 100000; i++) {
        pthread_mutex_lock(&lock);
        plain_counter++;                         /* critical section */
        pthread_mutex_unlock(&lock);
        atomic_fetch_add(&atomic_counter, 1);    /* lock-free alternative */
    }
    return NULL;
}

int main(void) {
    pthread_t t[4];
    for (int i = 0; i < 4; i++) pthread_create(&t[i], NULL, work, NULL);
    for (int i = 0; i < 4; i++) pthread_join(t[i], NULL);
    printf("mutex: %ld, atomic: %ld\n", plain_counter, atomic_load(&atomic_counter));
    return 0;
}
Output
mutex: 400000, atomic: 400000
ToolUse
pthread_mutex_tMutual exclusion around a critical section
pthread_cond_tWait for a condition (producer/consumer)
pthread_rwlock_tMany readers or one writer
_Atomic / atomic_intLock-free counters, flags
_Thread_localPer-thread variables
pthread_once / call_onceOne-time initialisation

What is a race condition, and what is a deadlock?

Is volatile enough for sharing a flag between threads?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report