Concurrency Basics: Locks and Semaphores
When multiple threads share the same memory space, they can interfere with each other. A race condition occurs when the correctness of a program depends on the timing of thread execution.
A Race Condition Example
Imagine a bank account with balance $100. Two threads each try to withdraw $50 at the same time:
// Thread 1: reads balance (100), computes 100 - 50 = 50
// Thread 2: reads balance (100), computes 100 - 50 = 50
// Thread 1: writes 50
// Thread 2: writes 50 ← should be 0, not 50!Both threads read the balance before either writes — a classic race condition. The final balance is $50 instead of $0.
Mutexes
A mutex (mutual exclusion) ensures that only one thread can enter a critical section at a time.
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&lock);
balance = balance - 50; // critical section
pthread_mutex_unlock(&lock);If another thread tries to lock the same mutex while it is held, it blocks until the holder releases it. Mutexes can also be spinlocks, where the waiting thread loops (spins) instead of blocking — useful for very short critical sections on multi-core systems.
Semaphores
A semaphore generalizes the mutex concept to allow N threads to enter a critical section simultaneously, rather than just one. A semaphore maintains a counter:
sem_wait()— decrements the counter; if it goes below zero, the thread blockssem_post()— increments the counter; if threads are waiting, one is woken
A semaphore initialized to 1 is equivalent to a mutex. A semaphore initialized to N allows up to N threads through.
Condition Variables
A condition variable allows threads to wait for a condition to become true. A thread calls pthread_cond_wait() which atomically unlocks the mutex and puts the thread to sleep. When another thread calls pthread_cond_signal(), the waiting thread wakes, re-locks the mutex, and checks the condition.
This pattern — lock a mutex, check a condition, wait if false, do work, signal, unlock — is the standard way to implement producer-consumer, readers-writers, and other concurrent patterns.
Deadlock Conditions (The Coffman Conditions)
Deadlock can occur when all four of these conditions hold simultaneously:
- Mutual exclusion — at least one resource cannot be shared
- Hold and wait — a thread holds one resource while waiting for another
- No preemption — resources cannot be forcibly taken away
- Circular wait — a cycle of threads exists, each waiting for a resource held by the next
Learn about deadlock prevention in detail.
This article is part of the Operating Systems Intermediate learning path.