Concurrency and Synchronization

When multiple threads access shared data without coordination, the result is a race condition — the outcome depends on unpredictable timing. Synchronization primitives (mutexes, semaphores, condition variables) impose ordering to prevent this.

Why It Matters

Any multi-threaded program — web servers, databases, game engines — must handle shared state correctly. Race conditions are the hardest bugs to reproduce and debug because they depend on scheduling timing. Understanding sync primitives and deadlock is non-negotiable for systems work.

The Problem: Race Conditions

int counter = 0;
 
// Thread 1                    // Thread 2
// load counter → 0            // load counter → 0
// add 1 → 1                   // add 1 → 1
// store → counter = 1         // store → counter = 1
// Expected: 2, Got: 1

The read-modify-write is not atomic. Two threads interleave at the instruction level.

Synchronization Primitives

Mutex (Mutual Exclusion)

Only one thread can hold the lock at a time. Others block until it’s released.

#include <pthread.h>
 
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int counter = 0;
 
void *increment(void *arg) {
    for (int i = 0; i < 1000000; i++) {
        pthread_mutex_lock(&lock);
        counter++;  // safe: only one thread at a time
        pthread_mutex_unlock(&lock);
    }
    return NULL;
}

Semaphore

A counter that allows up to N concurrent accessors. Mutex is a semaphore with N=1.

#include <semaphore.h>
 
sem_t sem;
sem_init(&sem, 0, 3);  // allow 3 concurrent threads
 
sem_wait(&sem);    // decrement (blocks if count == 0)
// critical section
sem_post(&sem);    // increment

Spinlock

Like a mutex but busy-waits instead of sleeping. Good when hold time is very short (nanoseconds) and context switch cost exceeds spin cost.

pthread_spinlock_t slock;
pthread_spin_init(&slock, 0);
 
pthread_spin_lock(&slock);    // busy-wait loop
// very short critical section
pthread_spin_unlock(&slock);

Comparison

PrimitiveBlockingBest For
MutexSleeps (yields CPU)General purpose, longer critical sections
SpinlockBusy-waitsVery short critical sections, real-time
SemaphoreSleepsResource counting (connection pools, etc.)
rwlockSleepsMany readers, few writers

Condition Variables

Wait for a condition to become true, without busy-waiting. Always paired with a mutex.

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int ready = 0;
 
// Consumer — waits for data
pthread_mutex_lock(&lock);
while (!ready)                          // MUST be while, not if (spurious wakeups)
    pthread_cond_wait(&cond, &lock);    // atomically: unlock + sleep + re-lock on wake
// use the data
pthread_mutex_unlock(&lock);
 
// Producer — signals data is ready
pthread_mutex_lock(&lock);
ready = 1;
pthread_cond_signal(&cond);   // wake one waiter (broadcast wakes all)
pthread_mutex_unlock(&lock);

Producer-Consumer Pattern

#define BUFSIZE 16
int buffer[BUFSIZE];
int count = 0;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t not_empty = PTHREAD_COND_INITIALIZER;
pthread_cond_t not_full = PTHREAD_COND_INITIALIZER;
 
void produce(int item) {
    pthread_mutex_lock(&lock);
    while (count == BUFSIZE)
        pthread_cond_wait(&not_full, &lock);
    buffer[count++] = item;
    pthread_cond_signal(&not_empty);
    pthread_mutex_unlock(&lock);
}
 
int consume(void) {
    pthread_mutex_lock(&lock);
    while (count == 0)
        pthread_cond_wait(&not_empty, &lock);
    int item = buffer[--count];
    pthread_cond_signal(&not_full);
    pthread_mutex_unlock(&lock);
    return item;
}

Deadlock

Four conditions (all must hold simultaneously):

ConditionMeaning
Mutual exclusionResource can only be held by one thread
Hold and waitThread holds one lock while waiting for another
No preemptionLocks can’t be forcibly taken
Circular waitA → waits for B → waits for A

Prevention: break any one condition. The most practical:

  • Lock ordering: always acquire locks in a consistent global order
  • Try-lock with backoff: pthread_mutex_trylock — if it fails, release all locks and retry