Introduction to POSIX Threads (pthreads)
POSIX threads (pthreads) is a standardized C language threading API for Unix-like operating systems. It provides a powerful interface for creating and managing multiple threads of execution within a single process, enabling parallel processing and improved performance on multi-core systems.
Threading Architecture Overview
Multithreading Architecture ├── Thread Management │ ├── Creation (pthread_create) │ ├── Termination (pthread_exit) │ ├── Joining (pthread_join) │ └── Detaching (pthread_detach) ├── Synchronization │ ├── Mutexes (pthread_mutex) │ ├── Condition Variables │ ├── Read-Write Locks │ ├── Barriers │ └── Semaphores └── Thread-local Storage ├── Thread-specific data └── Thread-local variables
Thread States
Created ↓ Runnable ←→ Running ↓ ↓ Blocked Terminated
Basic Thread Operations
1. Creating and Joining Threads
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> // Thread function void* thread_function(void* arg) { int thread_num = *(int*)arg; for (int i = 0; i < 5; i++) { printf("Thread %d: iteration %d\n", thread_num, i); sleep(1); // Simulate work } printf("Thread %d finished\n", thread_num); return NULL; } int main() { pthread_t thread1, thread2; int t1_arg = 1, t2_arg = 2; printf("Main: Creating threads\n"); // Create threads if (pthread_create(&thread1, NULL, thread_function, &t1_arg) != 0) { perror("Failed to create thread 1"); return 1; } if (pthread_create(&thread2, NULL, thread_function, &t2_arg) != 0) { perror("Failed to create thread 2"); return 1; } printf("Main: Waiting for threads to finish\n"); // Wait for threads to complete pthread_join(thread1, NULL); pthread_join(thread2, NULL); printf("Main: All threads finished\n"); return 0; } Compilation:
gcc -pthread threads.c -o threads ./threads
2. Thread with Return Values
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> typedef struct { int start; int end; int result; } ThreadData; void* sum_range(void* arg) { ThreadData* data = (ThreadData*)arg; data->result = 0; printf("Thread computing sum from %d to %d\n", data->start, data->end); for (int i = data->start; i <= data->end; i++) { data->result += i; usleep(1000); // Small delay to simulate work } printf("Thread finished: sum = %d\n", data->result); return (void*)&data->result; } int main() { pthread_t thread1, thread2; ThreadData data1 = {1, 50, 0}; ThreadData data2 = {51, 100, 0}; // Create threads pthread_create(&thread1, NULL, sum_range, &data1); pthread_create(&thread2, NULL, sum_range, &data2); // Wait for threads and get results int* result1; int* result2; pthread_join(thread1, (void**)&result1); pthread_join(thread2, (void**)&result2); int total = *result1 + *result2; printf("\nMain: Partial sums: %d + %d = %d\n", *result1, *result2, total); printf("Main: Expected sum 1-100 = 5050\n"); return 0; } 3. Thread Arguments and Context
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <string.h> #include <unistd.h> typedef struct { char name[50]; int id; int iterations; int delay_ms; } ThreadContext; void* worker_thread(void* arg) { ThreadContext* ctx = (ThreadContext*)arg; for (int i = 0; i < ctx->iterations; i++) { printf("[%s-%d] Working: iteration %d\n", ctx->name, ctx->id, i + 1); usleep(ctx->delay_ms * 1000); } printf("[%s-%d] Finished\n", ctx->name, ctx->id); // Create result string (must persist after function returns) char* result = malloc(100); sprintf(result, "%s-%d completed %d iterations", ctx->name, ctx->id, ctx->iterations); return result; } int main() { pthread_t threads[3]; ThreadContext contexts[3]; // Initialize thread contexts strcpy(contexts[0].name, "Worker"); contexts[0].id = 1; contexts[0].iterations = 3; contexts[0].delay_ms = 500; strcpy(contexts[1].name, "Helper"); contexts[1].id = 2; contexts[1].iterations = 5; contexts[1].delay_ms = 300; strcpy(contexts[2].name, "Daemon"); contexts[2].id = 3; contexts[2].iterations = 2; contexts[2].delay_ms = 800; printf("Main: Creating threads\n"); for (int i = 0; i < 3; i++) { pthread_create(&threads[i], NULL, worker_thread, &contexts[i]); } printf("Main: Waiting for threads\n"); // Collect results for (int i = 0; i < 3; i++) { char* result; pthread_join(threads[i], (void**)&result); printf("Thread %d result: %s\n", i + 1, result); free(result); } printf("Main: All done\n"); return 0; } Thread Synchronization
1. Mutex Basics
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #define NUM_THREADS 5 #define NUM_ITERATIONS 100000 // Shared resource long long counter = 0; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // Thread function with mutex protection void* increment_with_mutex(void* arg) { int thread_id = *(int*)arg; for (int i = 0; i < NUM_ITERATIONS; i++) { pthread_mutex_lock(&mutex); counter++; // Critical section pthread_mutex_unlock(&mutex); } printf("Thread %d finished\n", thread_id); return NULL; } // Thread function without mutex (race condition demo) void* increment_without_mutex(void* arg) { int thread_id = *(int*)arg; for (int i = 0; i < NUM_ITERATIONS; i++) { counter++; // RACE CONDITION! } printf("Thread %d finished (unsafe)\n", thread_id); return NULL; } int main() { pthread_t threads[NUM_THREADS]; int thread_ids[NUM_THREADS]; printf("=== Mutex Protection Demo ===\n\n"); // Test with mutex counter = 0; printf("With mutex protection:\n"); for (int i = 0; i < NUM_THREADS; i++) { thread_ids[i] = i; pthread_create(&threads[i], NULL, increment_with_mutex, &thread_ids[i]); } for (int i = 0; i < NUM_THREADS; i++) { pthread_join(threads[i], NULL); } printf("Final counter: %lld (expected: %d)\n\n", counter, NUM_THREADS * NUM_ITERATIONS); // Test without mutex counter = 0; printf("Without mutex protection (race condition):\n"); for (int i = 0; i < NUM_THREADS; i++) { pthread_create(&threads[i], NULL, increment_without_mutex, &thread_ids[i]); } for (int i = 0; i < NUM_THREADS; i++) { pthread_join(threads[i], NULL); } printf("Final counter: %lld (expected: %d)\n", counter, NUM_THREADS * NUM_ITERATIONS); pthread_mutex_destroy(&mutex); return 0; } 2. Mutex with Error Handling
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <errno.h> typedef struct { pthread_mutex_t mutex; int* data; int size; int count; } SafeArray; SafeArray* create_safe_array(int size) { SafeArray* arr = malloc(sizeof(SafeArray)); if (!arr) return NULL; // Initialize mutex with attributes pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); if (pthread_mutex_init(&arr->mutex, &attr) != 0) { free(arr); return NULL; } arr->data = calloc(size, sizeof(int)); arr->size = size; arr->count = 0; pthread_mutexattr_destroy(&attr); return arr; } int safe_array_add(SafeArray* arr, int value) { int ret = pthread_mutex_lock(&arr->mutex); if (ret != 0) { errno = ret; return -1; } if (arr->count >= arr->size) { pthread_mutex_unlock(&arr->mutex); return -1; // Array full } arr->data[arr->count++] = value; pthread_mutex_unlock(&arr->mutex); return 0; } int safe_array_get(SafeArray* arr, int index, int* value) { if (index < 0 || index >= arr->size) { return -1; } pthread_mutex_lock(&arr->mutex); if (index >= arr->count) { pthread_mutex_unlock(&arr->mutex); return -1; // Element not yet added } *value = arr->data[index]; pthread_mutex_unlock(&arr->mutex); return 0; } void safe_array_destroy(SafeArray* arr) { if (arr) { pthread_mutex_destroy(&arr->mutex); free(arr->data); free(arr); } } void* producer_thread(void* arg) { SafeArray* arr = (SafeArray*)arg; for (int i = 0; i < 10; i++) { if (safe_array_add(arr, i * 10) == 0) { printf("Producer added: %d\n", i * 10); } else { printf("Producer failed to add %d (array full)\n", i * 10); } usleep(100000); // 100ms delay } return NULL; } void* consumer_thread(void* arg) { SafeArray* arr = (SafeArray*)arg; int value; for (int i = 0; i < 10; i++) { if (safe_array_get(arr, i, &value) == 0) { printf("Consumer got: %d\n", value); } else { printf("Consumer failed to get element %d\n", i); } usleep(150000); // 150ms delay } return NULL; } int main() { SafeArray* arr = create_safe_array(10); if (!arr) { printf("Failed to create safe array\n"); return 1; } pthread_t producer, consumer; pthread_create(&producer, NULL, producer_thread, arr); pthread_create(&consumer, NULL, consumer_thread, arr); pthread_join(producer, NULL); pthread_join(consumer, NULL); safe_array_destroy(arr); return 0; } 3. Condition Variables
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #define BUFFER_SIZE 5 typedef struct { int buffer[BUFFER_SIZE]; int count; int in; int out; pthread_mutex_t mutex; pthread_cond_t not_full; pthread_cond_t not_empty; } BoundedBuffer; void buffer_init(BoundedBuffer* buf) { buf->count = 0; buf->in = 0; buf->out = 0; pthread_mutex_init(&buf->mutex, NULL); pthread_cond_init(&buf->not_full, NULL); pthread_cond_init(&buf->not_empty, NULL); } void buffer_destroy(BoundedBuffer* buf) { pthread_mutex_destroy(&buf->mutex); pthread_cond_destroy(&buf->not_full); pthread_cond_destroy(&buf->not_empty); } void buffer_put(BoundedBuffer* buf, int item) { pthread_mutex_lock(&buf->mutex); // Wait while buffer is full while (buf->count == BUFFER_SIZE) { printf("Buffer full, producer waiting...\n"); pthread_cond_wait(&buf->not_full, &buf->mutex); } // Add item to buffer buf->buffer[buf->in] = item; buf->in = (buf->in + 1) % BUFFER_SIZE; buf->count++; printf("Produced: %d (count: %d)\n", item, buf->count); // Signal that buffer is not empty pthread_cond_signal(&buf->not_empty); pthread_mutex_unlock(&buf->mutex); } int buffer_get(BoundedBuffer* buf) { pthread_mutex_lock(&buf->mutex); // Wait while buffer is empty while (buf->count == 0) { printf("Buffer empty, consumer waiting...\n"); pthread_cond_wait(&buf->not_empty, &buf->mutex); } // Get item from buffer int item = buf->buffer[buf->out]; buf->out = (buf->out + 1) % BUFFER_SIZE; buf->count--; printf("Consumed: %d (count: %d)\n", item, buf->count); // Signal that buffer is not full pthread_cond_signal(&buf->not_full); pthread_mutex_unlock(&buf->mutex); return item; } void* producer(void* arg) { BoundedBuffer* buf = (BoundedBuffer*)arg; for (int i = 0; i < 20; i++) { buffer_put(buf, i); usleep(rand() % 500000); // Random delay 0-500ms } return NULL; } void* consumer(void* arg) { BoundedBuffer* buf = (BoundedBuffer*)arg; for (int i = 0; i < 20; i++) { int item = buffer_get(buf); usleep(rand() % 800000); // Random delay 0-800ms } return NULL; } int main() { srand(time(NULL)); BoundedBuffer buffer; buffer_init(&buffer); pthread_t prod_thread, cons_thread; printf("Starting producer-consumer demo (buffer size: %d)\n\n", BUFFER_SIZE); pthread_create(&prod_thread, NULL, producer, &buffer); pthread_create(&cons_thread, NULL, consumer, &buffer); pthread_join(prod_thread, NULL); pthread_join(cons_thread, NULL); buffer_destroy(&buffer); printf("\nProducer-consumer finished\n"); return 0; } 4. Read-Write Locks
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> typedef struct { int data; int readers_active; int writers_active; pthread_rwlock_t rwlock; } SharedData; void init_data(SharedData* sd) { sd->data = 0; sd->readers_active = 0; sd->writers_active = 0; pthread_rwlock_init(&sd->rwlock, NULL); } void destroy_data(SharedData* sd) { pthread_rwlock_destroy(&sd->rwlock); } void* reader_thread(void* arg) { SharedData* sd = (SharedData*)arg; int thread_id = rand() % 100; for (int i = 0; i < 5; i++) { // Acquire read lock pthread_rwlock_rdlock(&sd->rwlock); // Reading sd->readers_active++; printf("Reader %d: reading value %d (readers: %d, writers: %d)\n", thread_id, sd->data, sd->readers_active, sd->writers_active); sd->readers_active--; // Release read lock pthread_rwlock_unlock(&sd->rwlock); usleep(rand() % 500000); } return NULL; } void* writer_thread(void* arg) { SharedData* sd = (SharedData*)arg; int thread_id = rand() % 100; for (int i = 0; i < 3; i++) { // Acquire write lock pthread_rwlock_wrlock(&sd->rwlock); // Writing sd->writers_active++; sd->data += 10; printf("Writer %d: writing value %d (readers: %d, writers: %d)\n", thread_id, sd->data, sd->readers_active, sd->writers_active); sd->writers_active--; // Release write lock pthread_rwlock_unlock(&sd->rwlock); usleep(rand() % 800000); } return NULL; } int main() { srand(time(NULL)); SharedData sd; init_data(&sd); pthread_t readers[5], writers[2]; printf("=== Read-Write Lock Demo ===\n\n"); // Create reader threads for (int i = 0; i < 5; i++) { pthread_create(&readers[i], NULL, reader_thread, &sd); } // Create writer threads for (int i = 0; i < 2; i++) { pthread_create(&writers[i], NULL, writer_thread, &sd); } // Wait for all threads for (int i = 0; i < 5; i++) { pthread_join(readers[i], NULL); } for (int i = 0; i < 2; i++) { pthread_join(writers[i], NULL); } printf("\nFinal value: %d\n", sd.data); destroy_data(&sd); return 0; } Advanced Threading Patterns
1. Thread Pool
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #define THREAD_POOL_SIZE 4 #define TASK_QUEUE_SIZE 20 typedef struct { void (*function)(void*); void* argument; } Task; typedef struct { Task task_queue[TASK_QUEUE_SIZE]; int queue_size; int head; int tail; pthread_mutex_t mutex; pthread_cond_t not_empty; pthread_cond_t not_full; pthread_t threads[THREAD_POOL_SIZE]; int shutdown; } ThreadPool; ThreadPool* thread_pool_create() { ThreadPool* pool = malloc(sizeof(ThreadPool)); pool->queue_size = 0; pool->head = 0; pool->tail = 0; pool->shutdown = 0; pthread_mutex_init(&pool->mutex, NULL); pthread_cond_init(&pool->not_empty, NULL); pthread_cond_init(&pool->not_full, NULL); return pool; } void* worker_thread(void* arg) { ThreadPool* pool = (ThreadPool*)arg; while (1) { pthread_mutex_lock(&pool->mutex); // Wait while queue is empty and not shutting down while (pool->queue_size == 0 && !pool->shutdown) { pthread_cond_wait(&pool->not_empty, &pool->mutex); } if (pool->shutdown) { pthread_mutex_unlock(&pool->mutex); break; } // Get task from queue Task task = pool->task_queue[pool->head]; pool->head = (pool->head + 1) % TASK_QUEUE_SIZE; pool->queue_size--; pthread_cond_signal(&pool->not_full); pthread_mutex_unlock(&pool->mutex); // Execute task task.function(task.argument); } return NULL; } void thread_pool_start(ThreadPool* pool) { for (int i = 0; i < THREAD_POOL_SIZE; i++) { pthread_create(&pool->threads[i], NULL, worker_thread, pool); } } int thread_pool_submit(ThreadPool* pool, void (*function)(void*), void* arg) { pthread_mutex_lock(&pool->mutex); // Wait if queue is full while (pool->queue_size == TASK_QUEUE_SIZE) { pthread_cond_wait(&pool->not_full, &pool->mutex); } // Add task to queue pool->task_queue[pool->tail].function = function; pool->task_queue[pool->tail].argument = arg; pool->tail = (pool->tail + 1) % TASK_QUEUE_SIZE; pool->queue_size++; pthread_cond_signal(&pool->not_empty); pthread_mutex_unlock(&pool->mutex); return 0; } void thread_pool_shutdown(ThreadPool* pool) { pthread_mutex_lock(&pool->mutex); pool->shutdown = 1; pthread_cond_broadcast(&pool->not_empty); pthread_mutex_unlock(&pool->mutex); for (int i = 0; i < THREAD_POOL_SIZE; i++) { pthread_join(pool->threads[i], NULL); } pthread_mutex_destroy(&pool->mutex); pthread_cond_destroy(&pool->not_empty); pthread_cond_destroy(&pool->not_full); free(pool); } // Example task functions void print_task(void* arg) { int* num = (int*)arg; printf("Task %d executed by thread %ld\n", *num, pthread_self()); free(arg); } void compute_task(void* arg) { int* n = (int*)arg; int result = 0; for (int i = 0; i <= *n; i++) { result += i; } printf("Sum 0-%d = %d (thread %ld)\n", *n, result, pthread_self()); free(arg); } int main() { ThreadPool* pool = thread_pool_create(); thread_pool_start(pool); printf("=== Thread Pool Demo ===\n"); printf("Pool size: %d, Queue size: %d\n\n", THREAD_POOL_SIZE, TASK_QUEUE_SIZE); // Submit tasks for (int i = 0; i < 15; i++) { int* num = malloc(sizeof(int)); *num = i + 1; thread_pool_submit(pool, print_task, num); } for (int i = 0; i < 5; i++) { int* num = malloc(sizeof(int)); *num = i * 10; thread_pool_submit(pool, compute_task, num); } sleep(2); // Let tasks complete thread_pool_shutdown(pool); return 0; } 2. Barrier Synchronization
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #define NUM_THREADS 5 #define NUM_PHASES 3 typedef struct { pthread_barrier_t barrier; int phase_data[NUM_THREADS]; } SharedContext; void* worker_phase(void* arg) { SharedContext* ctx = (SharedContext*)arg; long thread_id = (long)arg % NUM_THREADS; for (int phase = 0; phase < NUM_PHASES; phase++) { // Phase 1: Compute printf("Thread %ld: Starting phase %d\n", thread_id, phase); usleep(rand() % 500000); // Random work ctx->phase_data[thread_id] = rand() % 100; printf("Thread %ld: Completed phase %d, data = %d\n", thread_id, phase, ctx->phase_data[thread_id]); // Wait for all threads to complete phase pthread_barrier_wait(&ctx->barrier); // Phase 2: Process results (only one thread does this) if (thread_id == 0) { int sum = 0; for (int i = 0; i < NUM_THREADS; i++) { sum += ctx->phase_data[i]; } printf("Phase %d total sum: %d\n", phase, sum); } // Wait again before next phase pthread_barrier_wait(&ctx->barrier); } return NULL; } int main() { SharedContext ctx; pthread_t threads[NUM_THREADS]; // Initialize barrier pthread_barrier_init(&ctx.barrier, NULL, NUM_THREADS); printf("=== Barrier Synchronization Demo ===\n"); printf("%d threads, %d phases\n\n", NUM_THREADS, NUM_PHASES); // Create threads for (long i = 0; i < NUM_THREADS; i++) { pthread_create(&threads[i], NULL, worker_phase, (void*)i); } // Wait for threads for (int i = 0; i < NUM_THREADS; i++) { pthread_join(threads[i], NULL); } pthread_barrier_destroy(&ctx.barrier); printf("\nAll phases completed\n"); return 0; } 3. Thread-local Storage
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> // Thread-local storage using __thread keyword __thread int thread_local_counter = 0; // Thread-specific data using pthread_key_t pthread_key_t thread_specific_key; typedef struct { char name[50]; int id; } ThreadData; void destructor(void* value) { if (value) { ThreadData* data = (ThreadData*)value; printf("Destructor: Cleaning up thread %d (%s)\n", data->id, data->name); free(value); } } void* thread_function(void* arg) { long thread_id = (long)arg; // Using __thread local storage thread_local_counter++; printf("Thread %ld: thread_local_counter = %d\n", thread_id, thread_local_counter); // Using pthread_key_t thread-specific data ThreadData* data = malloc(sizeof(ThreadData)); sprintf(data->name, "Thread-%ld", thread_id); data->id = thread_id; pthread_setspecific(thread_specific_key, data); // Retrieve and use thread-specific data ThreadData* retrieved = pthread_getspecific(thread_specific_key); printf("Thread %ld: Retrieved data - name: %s, id: %d\n", thread_id, retrieved->name, retrieved->id); sleep(1); return NULL; } int main() { pthread_t threads[5]; // Create thread-specific data key pthread_key_create(&thread_specific_key, destructor); printf("=== Thread-Local Storage Demo ===\n\n"); // Create threads for (long i = 0; i < 5; i++) { pthread_create(&threads[i], NULL, thread_function, (void*)i); } // Wait for threads for (int i = 0; i < 5; i++) { pthread_join(threads[i], NULL); } // Delete key pthread_key_delete(thread_specific_key); 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 VERSION - DON'T DO THIS void* thread1_deadlock(void* arg) { printf("Thread 1: Locking A\n"); pthread_mutex_lock(&mutex_a); sleep(1); 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); 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("(Deadlock version commented out)\n\n"); // Uncomment to see deadlock /* printf("Creating deadlock threads...\n"); pthread_create(&t1, NULL, thread1_deadlock, NULL); pthread_create(&t2, NULL, thread2_deadlock, NULL); */ printf("Creating safe threads (consistent lock ordering)...\n"); pthread_create(&t1, NULL, thread1_safe, NULL); pthread_create(&t2, NULL, thread2_safe, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); printf("\nSafe version completed successfully\n"); return 0; } 2. Race Condition Detection
#include <stdio.h> #include <pthread.h> #include <assert.h> #define NUM_THREADS 10 #define NUM_ITERATIONS 10000 // Shared counter with race condition int shared_counter = 0; // Protected counter int protected_counter = 0; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // Atomic counter (using GCC atomic builtins) int atomic_counter = 0; void* race_thread(void* arg) { for (int i = 0; i < NUM_ITERATIONS; i++) { shared_counter++; // RACE CONDITION } return NULL; } void* protected_thread(void* arg) { for (int i = 0; i < NUM_ITERATIONS; i++) { pthread_mutex_lock(&mutex); protected_counter++; pthread_mutex_unlock(&mutex); } return NULL; } void* atomic_thread(void* arg) { for (int i = 0; i < NUM_ITERATIONS; i++) { __sync_fetch_and_add(&atomic_counter, 1); } return NULL; } void run_test(void* (*thread_func)(void*), const char* name) { pthread_t threads[NUM_THREADS]; if (thread_func == race_thread) { shared_counter = 0; } else if (thread_func == protected_thread) { protected_counter = 0; } else { atomic_counter = 0; } printf("\nTesting %s:\n", name); for (int i = 0; i < NUM_THREADS; i++) { pthread_create(&threads[i], NULL, thread_func, NULL); } for (int i = 0; i < NUM_THREADS; i++) { pthread_join(threads[i], NULL); } int expected = NUM_THREADS * NUM_ITERATIONS; int actual; if (thread_func == race_thread) { actual = shared_counter; } else if (thread_func == protected_thread) { actual = protected_counter; } else { actual = atomic_counter; } printf(" Expected: %d\n", expected); printf(" Actual: %d\n", actual); printf(" Difference: %d\n", expected - actual); } int main() { printf("=== Race Condition Detection ===\n"); printf("Threads: %d, Iterations per thread: %d\n", NUM_THREADS, NUM_ITERATIONS); run_test(race_thread, "Race condition (unsafe)"); run_test(protected_thread, "Mutex protected (safe)"); run_test(atomic_thread, "Atomic operations (safe)"); pthread_mutex_destroy(&mutex); return 0; } Performance Benchmarking
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <time.h> #define NUM_THREADS 4 #define ARRAY_SIZE 10000000 #define NUM_RUNS 5 typedef struct { int* array; int start; int end; long long result; } ThreadWork; void* sum_array_worker(void* arg) { ThreadWork* work = (ThreadWork*)arg; work->result = 0; for (int i = work->start; i < work->end; i++) { work->result += work->array[i]; } return NULL; } long long parallel_sum(int* array, int size, int num_threads) { pthread_t threads[num_threads]; ThreadWork works[num_threads]; int chunk_size = size / num_threads; // Create threads for (int i = 0; i < num_threads; i++) { works[i].array = array; works[i].start = i * chunk_size; works[i].end = (i == num_threads - 1) ? size : (i + 1) * chunk_size; works[i].result = 0; pthread_create(&threads[i], NULL, sum_array_worker, &works[i]); } // Wait for threads and sum results long long total = 0; for (int i = 0; i < num_threads; i++) { pthread_join(threads[i], NULL); total += works[i].result; } return total; } long long sequential_sum(int* array, int size) { long long total = 0; for (int i = 0; i < size; i++) { total += array[i]; } return total; } double get_time() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec + ts.tv_nsec / 1e9; } int main() { // Initialize array int* array = malloc(ARRAY_SIZE * sizeof(int)); for (int i = 0; i < ARRAY_SIZE; i++) { array[i] = rand() % 100; } printf("=== Performance Benchmark ===\n"); printf("Array size: %d\n", ARRAY_SIZE); printf("Threads: %d\n", NUM_THREADS); printf("Runs per test: %d\n\n", NUM_RUNS); // Sequential sum benchmark double seq_time = 0; long long seq_result = 0; for (int run = 0; run < NUM_RUNS; run++) { double start = get_time(); seq_result = sequential_sum(array, ARRAY_SIZE); double end = get_time(); seq_time += (end - start); } seq_time /= NUM_RUNS; // Parallel sum benchmark double par_time = 0; long long par_result = 0; for (int run = 0; run < NUM_RUNS; run++) { double start = get_time(); par_result = parallel_sum(array, ARRAY_SIZE, NUM_THREADS); double end = get_time(); par_time += (end - start); } par_time /= NUM_RUNS; printf("Sequential sum:\n"); printf(" Result: %lld\n", seq_result); printf(" Average time: %.4f seconds\n", seq_time); printf("\nParallel sum (%d threads):\n", NUM_THREADS); printf(" Result: %lld\n", par_result); printf(" Average time: %.4f seconds\n", par_time); printf(" Speedup: %.2fx\n", seq_time / par_time); // Verify results if (seq_result == par_result) { printf("\n✓ Results match\n"); } else { printf("\n✗ Results differ!\n"); printf(" Sequential: %lld\n", seq_result); printf(" Parallel: %lld\n", par_result); } free(array); return 0; } Best Practices Summary
Do's and Don'ts
// ✅ DO: Initialize synchronization primitives pthread_mutex_init(&mutex, NULL); pthread_cond_init(&cond, NULL); // ✅ DO: Always lock before accessing shared data pthread_mutex_lock(&mutex); shared_counter++; pthread_mutex_unlock(&mutex); // ✅ DO: Check return values if (pthread_create(&thread, NULL, func, arg) != 0) { perror("Failed to create thread"); } // ✅ DO: Join or detach threads pthread_join(thread, NULL); // Wait for thread // OR pthread_detach(thread); // Let thread clean up itself // ✅ DO: Use condition variables for signaling while (condition) { pthread_cond_wait(&cond, &mutex); } // ✅ DO: Destroy synchronization objects when done pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); // ❌ DON'T: Forget to unlock mutex pthread_mutex_lock(&mutex); // ... do work // Missing unlock! - will cause deadlock // ❌ DON'T: Access shared data without locking shared_data++; // RACE CONDITION! // ❌ DON'T: Hold locks while sleeping pthread_mutex_lock(&mutex); sleep(10); // Blocks other threads unnecessarily pthread_mutex_unlock(&mutex); // ❌ DON'T: Use different lock ordering // Thread 1: lock A then B // Thread 2: lock B then A // DEADLOCK! // ❌ DON'T: Signal condition without holding mutex pthread_cond_signal(&cond); // Should hold mutex Common pthread Functions Reference
| Function | Purpose |
|---|---|
pthread_create() | Create a new thread |
pthread_join() | Wait for thread termination |
pthread_exit() | Terminate calling thread |
pthread_self() | Get thread ID |
pthread_mutex_lock() | Lock mutex |
pthread_mutex_unlock() | Unlock mutex |
pthread_cond_wait() | Wait on condition |
pthread_cond_signal() | Signal one waiting thread |
pthread_cond_broadcast() | Signal all waiting threads |
pthread_rwlock_rdlock() | Acquire read lock |
pthread_rwlock_wrlock() | Acquire write lock |
pthread_barrier_wait() | Synchronize at barrier |
pthread_key_create() | Create thread-specific data key |
Conclusion
Multithreading with pthreads enables powerful parallel programming in C:
Key Concepts
- Thread creation and management - Create, join, detach threads
- Synchronization - Mutexes, condition variables, read-write locks
- Thread safety - Protect shared data from race conditions
- Deadlock prevention - Consistent lock ordering
- Performance - Leverage multiple cores for parallel processing
Best Practices
- Always synchronize access to shared data
- Use consistent lock ordering to prevent deadlocks
- Keep critical sections as small as possible
- Check return values from pthread functions
- Join or detach all created threads
- Destroy synchronization objects when done
- Consider thread safety in library design
- Profile and benchmark for optimal performance
Common Applications
- Parallel computation and data processing
- Server applications handling multiple clients
- GUI applications with background tasks
- Real-time systems with concurrent operations
- Producer-consumer problems
Mastering pthreads is essential for writing efficient, scalable, and responsive applications in C on Unix-like systems.
Complete C Programming Guide + Compilers Collection
1. C srand() Function – Understanding Seed Initialization
https://macronepal.com/understanding-the-c-srand-function
Explains how srand() initializes the pseudo-random number generator in C by setting a seed value. Using the same seed produces the same sequence, while time(NULL) gives different results each run.
2. C rand() Function Mechanics and Limitations
https://macronepal.com/c-rand-function-mechanics-and-limitations
Explains how rand() generates pseudo-random numbers between 0 and RAND_MAX, its deterministic nature, and limitations for security use cases.
3. C log() Function
https://macronepal.com/c-log-function-2
Covers natural logarithm calculation using <math.h> and its applications.
4. Mastering Date and Time in C
https://macronepal.com/mastering-date-and-time-in-c
Explains <time.h> functions like time(), clock(), difftime(), and struct tm.
5. Mastering time_t Type in C
https://macronepal.com/mastering-the-c-time_t-type-for-time-management
Explains time representation as seconds since Unix epoch and conversion functions.
6. C exp() Function
https://macronepal.com/c-exp-function-mechanics-and-implementation
Explains exponential function exp(x) and its scientific applications.
7. C log() Function (Alternate Guide)
https://macronepal.com/c-log-function
Comparison of log() and log10() with usage examples.
8. C log10() Function
https://macronepal.com/mastering-the-log10-function-in-c
Explains base-10 logarithm for engineering and scientific applications.
9. C tan() Function
https://macronepal.com/understanding-the-c-tan-function
Explains tangent function and radian-based calculations.
10. Random Numbers in C (Secure vs Predictable)
https://macronepal.com/mastering-c-random-numbers-for-secure-and-predictable-applications
Explains difference between rand() and secure randomness methods.
11. Free Online C Compiler
https://macronepal.com/free-online-c-code-compiler-2
Browser-based compiler for testing C programs instantly.
C Functions, Arguments, Parameters & Flow
Mastering Functions in C – Complete Guide
https://macronepal.com/c/mastering-functions-in-c-a-complete-guide/
Covers function structure, modular programming, and real-world usage.
Function Arguments in C
https://macronepal.com/c-function-arguments/
Explains how arguments are passed and used in function calls.
Function Parameters in C
https://macronepal.com/c-function-parameters/
Explains defining inputs for functions and matching them with arguments.
Function Declarations in C
https://macronepal.com/c-function-declarations-syntax-rules-and-best-practices/
Covers prototypes, syntax rules, and best practices.
Function Calls in C
https://macronepal.com/understanding-function-calls-in-c-syntax-mechanics-and-best-practices/
Explains execution flow and parameter handling during function calls.
Void Functions in C
https://macronepal.com/understanding-void-functions-in-c-syntax-patterns-and-best-practices/
Explains functions that do not return values.
Return Values in C
https://macronepal.com/c-return-values-mechanics-types-and-best-practices/
Explains different return types and how functions return results.
Pass-by-Value in C
https://macronepal.com/aws/understanding-pass-by-value-in-c-mechanics-implications-and-best-practices/
Explains how copies of variables are passed into functions.
Pass-by-Reference in C
https://macronepal.com/c/understanding-pass-by-reference-in-c-pointers-semantics-and-safe-practices/
Explains using pointers to modify original variables.
C strstr() Function
https://macronepal.com/aws/c-strstr-function/
Explains substring search inside strings in C.
C Preprocessor & Macros
https://macronepal.com/mastering-c-variadic-macros-for-flexible-debugging/
https://macronepal.com/mastering-the-stdc-macro-in-c/
https://macronepal.com/c-time-macro-mechanics-and-usage/
https://macronepal.com/understanding-the-c-date-macro/
https://macronepal.com/c-file-type/
https://macronepal.com/mastering-c-line-macro-for-debugging-and-diagnostics/
https://macronepal.com/mastering-predefined-macros-in-c/
https://macronepal.com/c-error-directive-mechanics-and-usage/
https://macronepal.com/understanding-the-c-pragma-directive/
https://macronepal.com/c-include-directive/
C Structures, Memory, Scope & Linkage
https://macronepal.com/mastering-structures-in-c/
https://macronepal.com/c-structure-declaration-mechanics-and-usage/
https://macronepal.com/c-structure-initialization-mechanics-and-best-practices/
https://macronepal.com/mastering-c-structure-member-access-for-reliable-data-handling/
https://macronepal.com/c-nested-structures/
https://macronepal.com/mastering-arrays-of-structures-in-c/
https://macronepal.com/c-structure-pointers-mechanics-and-implementation/
https://macronepal.com/understanding-c-structure-parameter-passing-mechanics/
https://macronepal.com/mastering-c-returning-structures-for-efficient-data-flow/
https://macronepal.com/c-self-referential-structures/
https://macronepal.com/mastering-structure-alignment-in-c/
https://macronepal.com/c-structure-padding-mechanics-and-optimization/
https://macronepal.com/understanding-c-flexible-array-members-mechanics-and-usage/
https://macronepal.com/mastering-c-anonymous-structures-for-flattened-data-layouts/
https://macronepal.com/c-unions/
https://macronepal.com/mastering-c-name-mangling-and-symbol-decoration/
https://macronepal.com/c-no-linkage-mechanics-and-scope-isolation/
https://macronepal.com/understanding-c-internal-linkage-mechanics-and-architecture/
C Scope, Storage Classes & Typedef
https://macronepal.com/mastering-function-prototype-scope-in-c/
https://macronepal.com/c-function-scope-mechanics-and-visibility/
https://macronepal.com/understanding-c-file-scope-mechanics-and-architecture/
https://macronepal.com/mastering-c-scope-rules-for-predictable-name-resolution/
https://macronepal.com/c-scope-rules/
https://macronepal.com/mastering-c-register-storage-class-for-historical-context-and-modern-alternatives/
https://macronepal.com/mastering-_thread_local-in-c/
https://macronepal.com/c-extern-storage-class-mechanics-and-usage/
https://macronepal.com/understanding-the-c-static-storage-class-mechanics-and-usage/
https://macronepal.com/c-auto-storage-class/
https://macronepal.com/c-typedef-with-pointers/
Extra Articles
https://macronepal.com/13757-2/
https://macronepal.com/13748-2/
https://macronepal.com/13747-2/
https://macronepal.com/13746-2/
https://macronepal.com/13745-2/
https://macronepal.com/13708-2/
https://macronepal.com/13707-2/
https://macronepal.com/13702-2/
Online Compilers
https://macronepal.com/free-html-online-code-compiler/
https://macronepal.com/free-online-python-code-compiler/
https://macronepal.com/free-online-python2-code-compiler/
https://macronepal.com/free-online-java-code-compiler/
https://macronepal.com/free-online-javascript-code-compiler/
https://macronepal.com/free-online-node-js-code-compiler/
https://macronepal.com/free-online-c-code-compiler/
https://macronepal.com/free-online-c-code-compiler-2/
https://macronepal.com/free-online-c-code-compiler-3/
https://macronepal.com/free-online-php-code-compiler/
https://macronepal.com/free-online-ruby-code-compiler/
https://macronepal.com/free-online-perl-code-compiler/
https://macronepal.com/free-online-lua-code-compiler/
https://macronepal.com/free-online-tcl-code-compiler/
https://macronepal.com/free-online-groovy-code-compiler/
https://macronepal.com/free-online-j-shell-code-compiler/
https://macronepal.com/free-online-haskell-code-compiler/
https://macronepal.com/free-online-scala-code-compiler/
https://macronepal.com/free-online-common-lisp-code-compiler/
https://macronepal.com/free-online-d-code-compiler/
https://macronepal.com/free-online-ada-code-compiler/
https://macronepal.com/free-erlang-code-compiler/
https://macronepal.com/free-online-assembly-code-compiler/
