A structured lesson workspace with readable content, hands-on examples, and a clean path to completion.
A mutex (mutual exclusion lock) serializes access to a shared resource. Before touching the shared data, a thread locks the mutex. If another thread holds the lock, the requesting thread blocks until the lock is released. After the operation, the thread unlocks the mutex. Only one thread can hold the lock at a time. The critical section, the code between lock and unlock, executes atomically with respect to other threads using the same mutex.
#include <stdio.h>#include <pthread.h>#define ITERATIONS 10000000int shared_counter = 0;pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;void *increment(void *arg) {(void)arg;for (int i = 0; i < ITERATIONS; i++) {pthread_mutex_lock(&counter_mutex);shared_counter++;pthread_mutex_unlock(&counter_mutex);}return NULL;}int main(void) {pthread_t t1, t2;pthread_create(&t1, NULL, increment, NULL);pthread_create(&t2, NULL, increment, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);printf("Expected: %d\n", 2 * ITERATIONS);printf("Actual: %d\n", shared_counter);pthread_mutex_destroy(&counter_mutex);return 0;}
Correct result every time. The mutex serializes the increments. Thread 1 locks, increments, unlocks. Thread 2 waits at the lock until thread 1 unlocks. No interleaving. No lost updates. The cost is performance: the threads now execute the critical section sequentially. The twenty million increments take longer than the unsynchronized version. Correctness costs cycles. That is the trade.
Every lock must have a matching unlock. Lock in the same order across threads to prevent deadlock. Keep the critical section as short as possible. Do not call functions that might block while holding a lock. Do not hold a lock across a sleep or I/O operation. Initialize with PTHREAD_MUTEX_INITIALIZER for static mutexes or pthread_mutex_init for dynamic ones. Destroy with pthread_mutex_destroy when done.
Deadlock occurs when thread A holds lock 1 and waits for lock 2, while thread B holds lock 2 and waits for lock 1. Neither can proceed. The program hangs forever. The prevention is simple: all threads acquire multiple locks in the same global order. If every thread locks mutex_A before mutex_B, the circular wait cannot form.
Mutexes are the fundamental synchronization primitive. Every shared mutable variable needs one, or needs to be documented as thread-local. The discipline is non-negotiable in concurrent C. Next: detached threads, which release their resources automatically without requiring a join.
Finish the lesson once you have worked through the material. This awards ★ 40 XP.