Linux System Programming · intermediate · ~25 min

POSIX threads — pthread_create, mutex, cond

Spawn threads, share state safely with a mutex, and coordinate threads with a condition variable.

Overview

POSIX threads (pthreads) create lightweight execution contexts that all share the same memory.

Because threads share memory, you must protect shared data:

  • Use a mutex (mutual-exclusion lock) to guard shared state so only one thread touches it at a time.
  • Use a condition variable to let threads wait for and signal each other.

Why it matters

Multi-threaded C is everywhere:

  • Server worker pools
  • GUI event loops
  • Parallel data processing

Pthreads is the standard threading API for the C language.

Core concepts

Create and join

  • pthread_create(&tid, attr, fn, arg) starts a new thread.
  • pthread_join(tid, &ret) waits for that thread to finish and collects its return value.

Mutex

A mutex (mutual-exclusion lock) ensures only one thread runs a critical section at a time.

  • pthread_mutex_lock / pthread_mutex_unlock mark the start and end of the protected region.
  • PTHREAD_MUTEX_INITIALIZER initializes a mutex statically.

Condition variable

A condition variable lets a thread wait for something to become true.

  • A thread waits under a lock; another thread signals it.
  • This wait-and-signal pair is how producer/consumer queues work.

Detached threads

pthread_detach(tid) means: "I don't need this thread's return value; reclaim its resources as soon as it exits."

Pentester mindset

Race conditions in concurrent code become real CVEs.

A "check then act" sequence shared across threads without a mutex is a TOCTOU (time-of-check to time-of-use) bug at the thread level.

Defensive coding habits

  • Always check the return value of pthread_* functions.
  • Always pair every lock with an unlock.
  • Call pthread_mutex_destroy during 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.

The core operations are:

  • Create a thread with pthread_create.
  • Join (wait for) a thread with pthread_join.
  • Protect shared state with a pthread_mutex_t.
  • Signal between threads with a 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.
  • In gdb: info threads lists all threads; thread N switches to thread N.

Memory safety

All threads share the heap.

  • Anything shared between threads must be guarded by a mutex or atomic.
  • volatile is not a substitute for a mutex or an atomic. It does not provide thread safety.

Real-world uses

C-language concurrency appears almost everywhere:

  • nginx (limited use)
  • redis (limited use)
  • most C++ projects
  • every C database driver

Practice tasks

  1. Have two threads increment a shared counter under a mutex.
  2. Build a producer/consumer queue using a condition variable.
  3. Use pthread_once for one-shot initialization.

Summary

  • Create, join, mutex, condition variable are the core building blocks.
  • Guard every shared read and write with a mutex.
  • Always pair each lock with an unlock.

Practice with these exercises