Mutex Usage in C: Complete Guide

Introduction to Mutex

A mutex (mutual exclusion) is a synchronization primitive used to protect shared resources from concurrent access in multithreaded programs. It ensures that only one thread can access a critical section at a time, preventing race conditions and data corruption.


Mutex Architecture Overview

Mutex Architecture
├── Mutex States
│   ├── Unlocked - Available for locking
│   ├── Locked - Owned by a thread
│   └── Locked with waiters - Threads waiting
├── Mutex Types
│   ├── Normal (fast)
│   ├── Recursive
│   ├── Error-checking
│   └── Adaptive
├── Operations
│   ├── lock() - Acquire mutex
│   ├── unlock() - Release mutex
│   ├── trylock() - Non-blocking attempt
│   └── timedlock() - Lock with timeout
└── Thread Synchronization
├── Critical Section
├── Condition Variables
└── Read-Write Locks

Basic Mutex Operations

1. Initializing and Destroying Mutexes

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
pthread_mutex_t mutex;
int main() {
// Initialize mutex with default attributes
if (pthread_mutex_init(&mutex, NULL) != 0) {
perror("mutex_init");
return 1;
}
printf("Mutex initialized successfully\n");
// Use mutex here
// Destroy mutex when done
pthread_mutex_destroy(&mutex);
printf("Mutex destroyed\n");
return 0;
}

2. Static Initialization

#include <stdio.h>
#include <pthread.h>
// Static initialization (simpler)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int main() {
printf("Mutex statically initialized\n");
// No need to call pthread_mutex_init
// Use mutex here
// Still need to destroy
pthread_mutex_destroy(&mutex);
return 0;
}

Basic Mutex Usage Examples

1. Protecting a Shared Counter

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#define NUM_THREADS 5
#define NUM_INCREMENTS 1000000
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
long long shared_counter = 0;
void* increment_counter(void* arg) {
int thread_id = *(int*)arg;
for (int i = 0; i < NUM_INCREMENTS; i++) {
// Lock mutex before accessing shared resource
pthread_mutex_lock(&mutex);
shared_counter++;
pthread_mutex_unlock(&mutex);
}
printf("Thread %d finished\n", thread_id);
return NULL;
}
int main() {
pthread_t threads[NUM_THREADS];
int thread_ids[NUM_THREADS];
printf("Starting %d threads each incrementing %d times\n",
NUM_THREADS, NUM_INCREMENTS);
printf("Expected final count: %d\n", 
NUM_THREADS * NUM_INCREMENTS);
// Create threads
for (int i = 0; i < NUM_THREADS; i++) {
thread_ids[i] = i;
if (pthread_create(&threads[i], NULL, 
increment_counter, &thread_ids[i]) != 0) {
perror("pthread_create");
return 1;
}
}
// Wait for threads to complete
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
printf("Final counter value: %lld\n", shared_counter);
printf("Expected value: %d\n", NUM_THREADS * NUM_INCREMENTS);
pthread_mutex_destroy(&mutex);
return 0;
}

2. Without Mutex - Race Condition Demo

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#define NUM_THREADS 5
#define NUM_INCREMENTS 100000
long long shared_counter = 0;  // No mutex protection
void* unsafe_increment(void* arg) {
int thread_id = *(int*)arg;
for (int i = 0; i < NUM_INCREMENTS; i++) {
shared_counter++;  // RACE CONDITION!
}
printf("Thread %d finished\n", thread_id);
return NULL;
}
int main() {
pthread_t threads[NUM_THREADS];
int thread_ids[NUM_THREADS];
printf("=== RACE CONDITION DEMO ===\n");
printf("Running without mutex protection\n\n");
// Create threads
for (int i = 0; i < NUM_THREADS; i++) {
thread_ids[i] = i;
pthread_create(&threads[i], NULL, 
unsafe_increment, &thread_ids[i]);
}
// Wait for threads
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
printf("\nExpected count: %d\n", 
NUM_THREADS * NUM_INCREMENTS);
printf("Actual count:   %lld\n", shared_counter);
printf("Difference:     %lld\n", 
(NUM_THREADS * NUM_INCREMENTS) - shared_counter);
return 0;
}

Mutex with trylock and timedlock

1. pthread_mutex_trylock() Example

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* trylock_worker(void* arg) {
int thread_id = *(int*)arg;
int attempts = 0;
int max_attempts = 10;
while (attempts < max_attempts) {
// Try to lock without blocking
int ret = pthread_mutex_trylock(&mutex);
if (ret == 0) {
// Successfully acquired lock
printf("Thread %d acquired lock after %d attempts\n", 
thread_id, attempts);
// Do some work
sleep(1);
pthread_mutex_unlock(&mutex);
break;
} else if (ret == EBUSY) {
// Mutex is busy, try again
printf("Thread %d: mutex busy, attempt %d\n", 
thread_id, attempts + 1);
attempts++;
sleep(1);  // Wait before retrying
} else {
printf("Thread %d: trylock error: %d\n", 
thread_id, ret);
break;
}
}
if (attempts >= max_attempts) {
printf("Thread %d giving up after %d attempts\n", 
thread_id, max_attempts);
}
return NULL;
}
int main() {
pthread_t thread1, thread2;
int id1 = 1, id2 = 2;
printf("=== pthread_mutex_trylock() Demo ===\n");
pthread_create(&thread1, NULL, trylock_worker, &id1);
sleep(1);  // Give thread1 a head start
pthread_create(&thread2, NULL, trylock_worker, &id2);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}

2. pthread_mutex_timedlock() Example

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* timedlock_worker(void* arg) {
int thread_id = *(int*)arg;
struct timespec ts;
// Calculate timeout time (current time + 3 seconds)
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += 3;
printf("Thread %d: trying to acquire lock with 3s timeout\n", 
thread_id);
int ret = pthread_mutex_timedlock(&mutex, &ts);
if (ret == 0) {
printf("Thread %d: acquired lock\n", thread_id);
sleep(2);  // Hold lock for 2 seconds
pthread_mutex_unlock(&mutex);
printf("Thread %d: released lock\n", thread_id);
} else if (ret == ETIMEDOUT) {
printf("Thread %d: timed out waiting for lock\n", 
thread_id);
} else {
printf("Thread %d: error %d\n", thread_id, ret);
}
return NULL;
}
int main() {
pthread_t thread1, thread2;
int id1 = 1, id2 = 2;
printf("=== pthread_mutex_timedlock() Demo ===\n");
pthread_create(&thread1, NULL, timedlock_worker, &id1);
sleep(1);  // Give thread1 time to acquire lock
pthread_create(&thread2, NULL, timedlock_worker, &id2);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}

Mutex Attributes and Types

1. Setting Mutex Attributes

#include <stdio.h>
#include <pthread.h>
#include <errno.h>
void demonstrate_mutex_attributes() {
pthread_mutex_t mutex;
pthread_mutexattr_t attr;
// Initialize mutex attributes
pthread_mutexattr_init(&attr);
// Set mutex type
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
// Set protocol
pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);
// Set robustness (for robust mutexes)
pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
// Set process-shared attribute
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE);
// Initialize mutex with attributes
pthread_mutex_init(&mutex, &attr);
// Get attribute values to verify
int type;
pthread_mutexattr_gettype(&attr, &type);
printf("Mutex type: %d\n", type);
int protocol;
pthread_mutexattr_getprotocol(&attr, &protocol);
printf("Protocol: %d\n", protocol);
int robust;
pthread_mutexattr_getrobust(&attr, &robust);
printf("Robust: %d\n", robust);
int pshared;
pthread_mutexattr_getpshared(&attr, &pshared);
printf("Process-shared: %d\n", pshared);
// Cleanup
pthread_mutex_destroy(&mutex);
pthread_mutexattr_destroy(&attr);
}
int main() {
demonstrate_mutex_attributes();
return 0;
}

2. Different Mutex Types

#include <stdio.h>
#include <pthread.h>
#include <errno.h>
// Normal mutex - deadlocks if same thread tries to lock twice
void normal_mutex_demo() {
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
printf("\n=== Normal Mutex ===\n");
pthread_mutex_lock(&mutex);
printf("First lock acquired\n");
// This would deadlock with normal mutex
int ret = pthread_mutex_trylock(&mutex);
if (ret == EBUSY) {
printf("Second lock attempt would block (EBUSY)\n");
}
pthread_mutex_unlock(&mutex);
printf("Mutex unlocked\n");
}
// Recursive mutex - same thread can lock multiple times
void recursive_mutex_demo() {
pthread_mutex_t mutex;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&mutex, &attr);
printf("\n=== Recursive Mutex ===\n");
pthread_mutex_lock(&mutex);
printf("First lock acquired\n");
pthread_mutex_lock(&mutex);
printf("Second lock acquired (recursive)\n");
printf("Lock count: 2\n");
pthread_mutex_unlock(&mutex);
printf("One unlock, still locked\n");
pthread_mutex_unlock(&mutex);
printf("Second unlock, now unlocked\n");
pthread_mutex_destroy(&mutex);
pthread_mutexattr_destroy(&attr);
}
// Error-checking mutex - returns error on deadlock
void errorcheck_mutex_demo() {
pthread_mutex_t mutex;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
pthread_mutex_init(&mutex, &attr);
printf("\n=== Error-Checking Mutex ===\n");
pthread_mutex_lock(&mutex);
printf("First lock acquired\n");
int ret = pthread_mutex_lock(&mutex);
if (ret == EDEADLK) {
printf("Second lock attempt would deadlock (EDEADLK)\n");
}
pthread_mutex_unlock(&mutex);
printf("Mutex unlocked\n");
pthread_mutex_destroy(&mutex);
pthread_mutexattr_destroy(&attr);
}
int main() {
normal_mutex_demo();
recursive_mutex_demo();
errorcheck_mutex_demo();
return 0;
}

Advanced Mutex Patterns

1. Guard Pattern (RAII-style in C)

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
typedef struct {
pthread_mutex_t* mutex;
} MutexGuard;
// Automatically lock mutex on creation
MutexGuard* guard_create(pthread_mutex_t* mutex) {
MutexGuard* guard = malloc(sizeof(MutexGuard));
guard->mutex = mutex;
pthread_mutex_lock(guard->mutex);
printf("Mutex locked by guard\n");
return guard;
}
// Automatically unlock on destruction
void guard_destroy(MutexGuard* guard) {
pthread_mutex_unlock(guard->mutex);
printf("Mutex unlocked by guard\n");
free(guard);
}
// Example usage
void guarded_function(pthread_mutex_t* mutex) {
MutexGuard* guard = guard_create(mutex);
// Critical section - automatically protected
printf("In critical section\n");
// No need to manually unlock - guard handles it
guard_destroy(guard);
}
int main() {
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
printf("=== Guard Pattern Demo ===\n");
guarded_function(&mutex);
pthread_mutex_destroy(&mutex);
return 0;
}

2. Double-Checked Locking Pattern

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
typedef struct {
int* data;
int size;
int initialized;
pthread_mutex_t mutex;
} SharedResource;
SharedResource* resource = NULL;
pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
SharedResource* get_resource() {
// First check (no locking)
if (resource == NULL) {
// Second check with locking
pthread_mutex_lock(&init_mutex);
if (resource == NULL) {
printf("Initializing resource...\n");
resource = malloc(sizeof(SharedResource));
resource->size = 100;
resource->data = malloc(resource->size * sizeof(int));
resource->initialized = 1;
pthread_mutex_init(&resource->mutex, NULL);
// Initialize data
for (int i = 0; i < resource->size; i++) {
resource->data[i] = i * i;
}
sleep(1);  // Simulate initialization time
printf("Resource initialized\n");
}
pthread_mutex_unlock(&init_mutex);
}
return resource;
}
void* worker_thread(void* arg) {
int thread_id = *(int*)arg;
printf("Thread %d getting resource\n", thread_id);
SharedResource* res = get_resource();
pthread_mutex_lock(&res->mutex);
printf("Thread %d accessing resource\n", thread_id);
printf("  data[50] = %d\n", res->data[50]);
pthread_mutex_unlock(&res->mutex);
return NULL;
}
int main() {
pthread_t threads[5];
int thread_ids[5];
printf("=== Double-Checked Locking Demo ===\n");
for (int i = 0; i < 5; i++) {
thread_ids[i] = i;
pthread_create(&threads[i], NULL, worker_thread, &thread_ids[i]);
}
for (int i = 0; i < 5; i++) {
pthread_join(threads[i], NULL);
}
// Cleanup
if (resource) {
pthread_mutex_destroy(&resource->mutex);
free(resource->data);
free(resource);
}
pthread_mutex_destroy(&init_mutex);
return 0;
}

3. Hierarchical Locking (Lock Ordering)

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
typedef struct {
pthread_mutex_t mutex;
int level;
char* name;
} HierarchicalMutex;
HierarchicalMutex mutex1 = {PTHREAD_MUTEX_INITIALIZER, 1, "Mutex1"};
HierarchicalMutex mutex2 = {PTHREAD_MUTEX_INITIALIZER, 2, "Mutex2"};
HierarchicalMutex mutex3 = {PTHREAD_MUTEX_INITIALIZER, 3, "Mutex3"};
// Thread-local current lock level
__thread int current_level = 0;
void hierarchical_lock(HierarchicalMutex* hm) {
if (current_level > hm->level) {
printf("ERROR: Trying to acquire %s (level %d) while holding higher level %d\n",
hm->name, hm->level, current_level);
return;
}
pthread_mutex_lock(&hm->mutex);
current_level = hm->level;
printf("Acquired %s (level %d), current level now %d\n",
hm->name, hm->level, current_level);
}
void hierarchical_unlock(HierarchicalMutex* hm) {
if (current_level != hm->level) {
printf("ERROR: Trying to release %s but current level is %d\n",
hm->name, current_level);
return;
}
pthread_mutex_unlock(&hm->mutex);
current_level = 0;  // Reset for simplicity
printf("Released %s (level %d)\n", hm->name, hm->level);
}
void* worker1(void* arg) {
printf("\nWorker 1 - correct ordering\n");
hierarchical_lock(&mutex1);
sleep(1);
hierarchical_lock(&mutex2);
sleep(1);
hierarchical_lock(&mutex3);
hierarchical_unlock(&mutex3);
hierarchical_unlock(&mutex2);
hierarchical_unlock(&mutex1);
return NULL;
}
void* worker2(void* arg) {
printf("\nWorker 2 - incorrect ordering (will deadlock)\n");
hierarchical_lock(&mutex3);
sleep(1);
hierarchical_lock(&mutex2);  // This would deadlock if both run
sleep(1);
hierarchical_lock(&mutex1);
hierarchical_unlock(&mutex1);
hierarchical_unlock(&mutex2);
hierarchical_unlock(&mutex3);
return NULL;
}
int main() {
pthread_t t1, t2;
printf("=== Hierarchical Locking Demo ===\n");
printf("Lock levels: Mutex1=1, Mutex2=2, Mutex3=3\n");
printf("Always acquire locks in increasing level order\n\n");
// Run correct ordering
pthread_create(&t1, NULL, worker1, NULL);
pthread_join(t1, NULL);
printf("\n--- Now try incorrect ordering ---\n");
// Run incorrect ordering (commented to avoid deadlock)
// pthread_create(&t2, NULL, worker2, NULL);
// pthread_join(t2, NULL);
return 0;
}

Robust Mutexes

1. Handling Thread Death While Holding Mutex

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
pthread_mutex_t robust_mutex;
void* thread1(void* arg) {
pthread_mutex_lock(&robust_mutex);
printf("Thread 1 acquired robust mutex\n");
// Simulate crash without unlocking
printf("Thread 1 crashing while holding mutex...\n");
pthread_exit(NULL);  // Thread exits without unlocking
}
void* thread2(void* arg) {
sleep(1);  // Wait for thread1 to crash
printf("\nThread 2 attempting to lock robust mutex\n");
int ret = pthread_mutex_lock(&robust_mutex);
if (ret == EOWNERDEAD) {
printf("Thread 2: Previous owner died, recovering mutex\n");
// Mark mutex as consistent
pthread_mutex_consistent(&robust_mutex);
// Now we own the mutex
printf("Thread 2: Mutex recovered and locked\n");
// Do work
sleep(1);
pthread_mutex_unlock(&robust_mutex);
printf("Thread 2: Mutex unlocked\n");
} else if (ret == 0) {
printf("Thread 2: Mutex locked normally\n");
pthread_mutex_unlock(&robust_mutex);
} else {
printf("Thread 2: Error %d\n", ret);
}
return NULL;
}
int main() {
pthread_mutexattr_t attr;
pthread_t t1, t2;
// Initialize robust mutex
pthread_mutexattr_init(&attr);
pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
pthread_mutex_init(&robust_mutex, &attr);
printf("=== Robust Mutex Demo ===\n");
pthread_create(&t1, NULL, thread1, NULL);
pthread_create(&t2, NULL, thread2, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_mutex_destroy(&robust_mutex);
pthread_mutexattr_destroy(&attr);
return 0;
}

Performance Analysis

1. Mutex Performance Benchmark

#include <stdio.h>
#include <pthread.h>
#include <time.h>
#include <unistd.h>
#define NUM_THREADS 4
#define NUM_ITERATIONS 1000000
#define NUM_RUNS 5
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
long long counter = 0;
void* benchmark_worker(void* arg) {
for (int i = 0; i < NUM_ITERATIONS; i++) {
pthread_mutex_lock(&mutex);
counter++;
pthread_mutex_unlock(&mutex);
}
return NULL;
}
double run_benchmark() {
pthread_t threads[NUM_THREADS];
struct timespec start, end;
counter = 0;
clock_gettime(CLOCK_MONOTONIC, &start);
for (int i = 0; i < NUM_THREADS; i++) {
pthread_create(&threads[i], NULL, benchmark_worker, NULL);
}
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
clock_gettime(CLOCK_MONOTONIC, &end);
double elapsed = (end.tv_sec - start.tv_sec) + 
(end.tv_nsec - start.tv_nsec) / 1e9;
return elapsed;
}
int main() {
printf("=== Mutex Performance Benchmark ===\n");
printf("Threads: %d, Iterations per thread: %d\n\n", 
NUM_THREADS, NUM_ITERATIONS);
double total_time = 0;
for (int run = 1; run <= NUM_RUNS; run++) {
double time = run_benchmark();
total_time += time;
printf("Run %d: %.3f seconds\n", run, time);
}
double avg_time = total_time / NUM_RUNS;
long long total_ops = NUM_THREADS * NUM_ITERATIONS;
double ops_per_sec = total_ops / avg_time;
double ns_per_op = (avg_time * 1e9) / total_ops;
printf("\n=== Results ===\n");
printf("Average time: %.3f seconds\n", avg_time);
printf("Total operations: %lld\n", total_ops);
printf("Operations/second: %.0f\n", ops_per_sec);
printf("Nanoseconds per operation: %.1f\n", ns_per_op);
pthread_mutex_destroy(&mutex);
return 0;
}

2. Mutex vs Atomic Operations

#include <stdio.h>
#include <pthread.h>
#include <stdatomic.h>
#include <time.h>
#define NUM_THREADS 4
#define NUM_ITERATIONS 1000000
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
long long mutex_counter = 0;
atomic_long atomic_counter = 0;
void* mutex_worker(void* arg) {
for (int i = 0; i < NUM_ITERATIONS; i++) {
pthread_mutex_lock(&mutex);
mutex_counter++;
pthread_mutex_unlock(&mutex);
}
return NULL;
}
void* atomic_worker(void* arg) {
for (int i = 0; i < NUM_ITERATIONS; i++) {
atomic_fetch_add(&atomic_counter, 1);
}
return NULL;
}
double benchmark_mutex() {
pthread_t threads[NUM_THREADS];
struct timespec start, end;
mutex_counter = 0;
clock_gettime(CLOCK_MONOTONIC, &start);
for (int i = 0; i < NUM_THREADS; i++) {
pthread_create(&threads[i], NULL, mutex_worker, NULL);
}
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
clock_gettime(CLOCK_MONOTONIC, &end);
return (end.tv_sec - start.tv_sec) + 
(end.tv_nsec - start.tv_nsec) / 1e9;
}
double benchmark_atomic() {
pthread_t threads[NUM_THREADS];
struct timespec start, end;
atomic_counter = 0;
clock_gettime(CLOCK_MONOTONIC, &start);
for (int i = 0; i < NUM_THREADS; i++) {
pthread_create(&threads[i], NULL, atomic_worker, NULL);
}
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
clock_gettime(CLOCK_MONOTONIC, &end);
return (end.tv_sec - start.tv_sec) + 
(end.tv_nsec - start.tv_nsec) / 1e9;
}
int main() {
printf("=== Mutex vs Atomic Operations ===\n");
printf("Threads: %d, Iterations: %d\n\n", 
NUM_THREADS, NUM_ITERATIONS);
double mutex_time = benchmark_mutex();
double atomic_time = benchmark_atomic();
printf("Mutex time:  %.3f seconds\n", mutex_time);
printf("Atomic time: %.3f seconds\n", atomic_time);
printf("Atomic is %.2fx faster\n", mutex_time / atomic_time);
pthread_mutex_destroy(&mutex);
return 0;
}

Common Pitfalls and Solutions

1. Deadlock Example

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mutex_a = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex_b = PTHREAD_MUTEX_INITIALIZER;
// DEADLOCK EXAMPLE - DON'T DO THIS
void* thread1_deadlock(void* arg) {
printf("Thread 1: locking A\n");
pthread_mutex_lock(&mutex_a);
sleep(1);  // Give thread2 time to lock B
printf("Thread 1: trying to lock B\n");
pthread_mutex_lock(&mutex_b);  // DEADLOCK!
printf("Thread 1: got both locks\n");
pthread_mutex_unlock(&mutex_b);
pthread_mutex_unlock(&mutex_a);
return NULL;
}
void* thread2_deadlock(void* arg) {
printf("Thread 2: locking B\n");
pthread_mutex_lock(&mutex_b);
sleep(1);  // Give thread1 time to lock A
printf("Thread 2: trying to lock A\n");
pthread_mutex_lock(&mutex_a);  // DEADLOCK!
printf("Thread 2: got both locks\n");
pthread_mutex_unlock(&mutex_a);
pthread_mutex_unlock(&mutex_b);
return NULL;
}
// SOLUTION - consistent lock ordering
void* thread1_safe(void* arg) {
printf("Thread 1 (safe): locking A then B\n");
pthread_mutex_lock(&mutex_a);
pthread_mutex_lock(&mutex_b);
printf("Thread 1: working\n");
sleep(1);
pthread_mutex_unlock(&mutex_b);
pthread_mutex_unlock(&mutex_a);
return NULL;
}
void* thread2_safe(void* arg) {
printf("Thread 2 (safe): locking A then B (same order)\n");
pthread_mutex_lock(&mutex_a);  // Same order as thread1
pthread_mutex_lock(&mutex_b);
printf("Thread 2: working\n");
sleep(1);
pthread_mutex_unlock(&mutex_b);
pthread_mutex_unlock(&mutex_a);
return NULL;
}
int main() {
pthread_t t1, t2;
printf("=== DEADLOCK DEMO ===\n");
printf("(Commented out to avoid deadlock)\n\n");
// Uncomment to see deadlock
/*
pthread_create(&t1, NULL, thread1_deadlock, NULL);
pthread_create(&t2, NULL, thread2_deadlock, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
*/
printf("=== SAFE VERSION (same lock order) ===\n");
pthread_create(&t1, NULL, thread1_safe, NULL);
pthread_create(&t2, NULL, thread2_safe, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
return 0;
}

2. Lock Granularity

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#define ARRAY_SIZE 1000
#define NUM_THREADS 4
// Coarse-grained locking (single mutex for entire structure)
typedef struct {
int data[ARRAY_SIZE];
pthread_mutex_t mutex;
} CoarseArray;
// Fine-grained locking (mutex per element)
typedef struct {
int data[ARRAY_SIZE];
pthread_mutex_t mutexes[ARRAY_SIZE];
} FineArray;
CoarseArray coarse;
FineArray fine;
// Initialize structures
void init_structures() {
pthread_mutex_init(&coarse.mutex, NULL);
for (int i = 0; i < ARRAY_SIZE; i++) {
pthread_mutex_init(&fine.mutexes[i], NULL);
coarse.data[i] = i;
fine.data[i] = i;
}
}
// Coarse-grained worker
void* coarse_worker(void* arg) {
int id = *(int*)arg;
for (int i = 0; i < 100; i++) {
int idx = (id * 100 + i) % ARRAY_SIZE;
pthread_mutex_lock(&coarse.mutex);
coarse.data[idx]++;  // Lock entire array
pthread_mutex_unlock(&coarse.mutex);
}
return NULL;
}
// Fine-grained worker
void* fine_worker(void* arg) {
int id = *(int*)arg;
for (int i = 0; i < 100; i++) {
int idx = (id * 100 + i) % ARRAY_SIZE;
pthread_mutex_lock(&fine.mutexes[idx]);  // Lock only this element
fine.data[idx]++;  // Others can be accessed simultaneously
pthread_mutex_unlock(&fine.mutexes[idx]);
}
return NULL;
}
double benchmark_coarse() {
pthread_t threads[NUM_THREADS];
int ids[NUM_THREADS];
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);
for (int i = 0; i < NUM_THREADS; i++) {
ids[i] = i;
pthread_create(&threads[i], NULL, coarse_worker, &ids[i]);
}
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
clock_gettime(CLOCK_MONOTONIC, &end);
return (end.tv_sec - start.tv_sec) + 
(end.tv_nsec - start.tv_nsec) / 1e9;
}
double benchmark_fine() {
pthread_t threads[NUM_THREADS];
int ids[NUM_THREADS];
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);
for (int i = 0; i < NUM_THREADS; i++) {
ids[i] = i;
pthread_create(&threads[i], NULL, fine_worker, &ids[i]);
}
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
clock_gettime(CLOCK_MONOTONIC, &end);
return (end.tv_sec - start.tv_sec) + 
(end.tv_nsec - start.tv_nsec) / 1e9;
}
int main() {
init_structures();
printf("=== Lock Granularity Comparison ===\n");
printf("Array size: %d, Threads: %d\n\n", ARRAY_SIZE, NUM_THREADS);
double coarse_time = benchmark_coarse();
double fine_time = benchmark_fine();
printf("Coarse-grained time: %.3f seconds\n", coarse_time);
printf("Fine-grained time:   %.3f seconds\n", fine_time);
printf("Fine-grained is %.2fx faster\n", coarse_time / fine_time);
pthread_mutex_destroy(&coarse.mutex);
for (int i = 0; i < ARRAY_SIZE; i++) {
pthread_mutex_destroy(&fine.mutexes[i]);
}
return 0;
}

Best Practices Summary

Do's and Don'ts

// ✅ DO: Always lock before accessing shared data
pthread_mutex_lock(&mutex);
shared_counter++;
pthread_mutex_unlock(&mutex);
// ✅ DO: Use trylock for non-blocking attempts
if (pthread_mutex_trylock(&mutex) == 0) {
// Got lock
pthread_mutex_unlock(&mutex);
}
// ✅ DO: Use consistent lock ordering to prevent deadlock
// Always lock mutex A before mutex B
// ✅ DO: Keep critical sections as small as possible
pthread_mutex_lock(&mutex);
// Only essential operations
int temp = shared_data;
pthread_mutex_unlock(&mutex);
// Process temp outside lock
// ✅ DO: Check return values of mutex functions
int ret = pthread_mutex_lock(&mutex);
if (ret != 0) {
// Handle error
}
// ✅ DO: Use recursive mutexes when needed (but avoid if possible)
// ❌ DON'T: Forget to unlock mutex
pthread_mutex_lock(&mutex);
// do something
// Missing unlock! - will cause deadlock
// ❌ DON'T: Lock mutex twice in same thread (unless recursive)
pthread_mutex_lock(&mutex);
pthread_mutex_lock(&mutex);  // DEADLOCK!
// ❌ DON'T: Access shared data without locking
shared_data++;  // RACE CONDITION!
// ❌ DON'T: Hold locks while performing slow operations
pthread_mutex_lock(&mutex);
slow_operation();  // Blocks other threads unnecessarily
pthread_mutex_unlock(&mutex);
// ❌ DON'T: Destroy mutex while it's locked
pthread_mutex_destroy(&mutex);  // Undefined behavior if locked

Mutex Types Comparison

Mutex TypeLocking RulesUse CaseOverhead
NormalCan't relock in same threadGeneral purposeLowest
RecursiveCan relock in same threadRecursive functionsMedium
Error-checkingReturns error on misuseDebuggingHigher
AdaptiveSpins briefly then sleepsHigh contentionVariable
RobustHandles owner deathProcess robustnessHigher

Conclusion

Mutexes are fundamental to thread synchronization in C:

Key Concepts

  • Mutual exclusion - Only one thread can hold the lock
  • Critical sections - Code protected by mutex
  • Deadlock prevention - Consistent lock ordering
  • Lock granularity - Balance between contention and overhead
  • Robust mutexes - Handle thread termination

Best Practices

  1. Always pair lock/unlock operations
  2. Keep critical sections minimal
  3. Use consistent lock ordering
  4. Handle lock errors appropriately
  5. Consider lock-free alternatives for simple operations
  6. Test thoroughly for race conditions
  7. Profile to find contention points

When to Use Mutexes

  • Protecting shared data structures
  • Coordinating thread access to resources
  • Implementing thread-safe APIs
  • Managing shared state
  • Preventing race conditions

Mastering mutex usage is essential for writing correct and efficient multithreaded programs in C.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper