Linux System Programming · intermediate · ~25 min

POSIX threads — pthread_create, mutex, cond

Spawn threads, share state via mutex, signal via condition variable.

Overview

pthreads spawn lightweight execution contexts that share memory. You guard shared state with mutexes and coordinate via condition variables.

Why it matters

Multi-threaded C is everywhere — server worker pools, GUI event loops, parallel data crunching. Pthreads is the C-language API.

Core concepts

create + join. pthread_create(&tid, attr, fn, arg) spawns; pthread_join(tid, &ret) waits.

Mutex. pthread_mutex_lock/unlock. Holds for the critical section. PTHREAD_MUTEX_INITIALIZER is the static init.

Condition variable. Wait under a lock; another thread signals. The signal+wait pair is how producer/consumer queues work.

Detached threads. pthread_detach(tid) says 'I don't care about the return; reclaim immediately on exit'.

Pentester mindset. Race conditions in concurrent code are CVEs. A 'check then act' across threads without a mutex is a TOCTOU at thread level.

Defensive coding habit. Always check the return of pthread_* functions. Always pair lock with unlock. Use pthread_mutex_destroy at teardown.

Syntax notes

#include <pthread.h>
int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*fn)(void *), void *arg);
int pthread_join(pthread_t tid, void **retval);
int pthread_mutex_lock(pthread_mutex_t *m);
int pthread_cond_wait(pthread_cond_t *c, pthread_mutex_t *m);

Lesson

POSIX threads (pthreads) are the standard C concurrency API on Linux. You create with pthread_create, join with pthread_join, protect shared state with pthread_mutex_t, and signal between threads with pthread_cond_t.

Code examples

pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
int counter = 0;
void *bump(void *_) {
    pthread_mutex_lock(&m);
    counter++;
    pthread_mutex_unlock(&m);
    return NULL;
}
pthread_t t;
pthread_create(&t, NULL, bump, NULL);
pthread_join(t, NULL);

Line by line

pthread_mutex_lock(&m);                  /* acquire */
while (!queue_has_data(q))               /* spurious wakeups exist */
    pthread_cond_wait(&c, &m);           /* atomic release + wait */
item_t x = queue_pop(q);
pthread_mutex_unlock(&m);                /* release */
use(x);

Common mistakes

  • Reading shared state without holding the mutex.

Debugging tips

ThreadSanitizer (-fsanitize=thread) finds data races at runtime. gdb info threads lists all threads; thread N switches.

Memory safety

All threads share the heap. Anything shared must be guarded. volatile is NOT a substitute for a mutex or atomic.

Real-world uses

nginx (limited use), redis (limited), most C++ projects, every C database driver. Anywhere C-language concurrency lives.

Practice tasks

  1. Two threads bump a counter under a mutex. 2. Producer/consumer queue via cond var. 3. pthread_once for one-shot initialisation.

Summary

create + join + mutex + cond. Guard every shared read and write. Pair lock with unlock.

Practice with these exercises