Multithreading is a powerful programming technique that allows multiple threads of execution to run concurrently within a single process. In C, multithreading enables programs to perform multiple operations simultaneously, improving performance on multi-core systems and creating responsive applications. This comprehensive guide covers everything you need to know to start writing multithreaded C programs using the POSIX threads (pthreads) library.
What is Multithreading?
A thread is the smallest unit of execution within a process. While a process has its own memory space, threads within the same process share that memory space, including:
- Code (instructions)
- Heap memory (dynamically allocated data)
- Global variables
- File descriptors
Each thread has its own:
- Stack (local variables)
- Program counter
- Register set
- Thread ID
┌─────────────────────────────────────┐ │ Process │ │ ┌─────────┐ ┌─────────┐ │ │ │ Thread 1│ │ Thread 2│ │ │ │ Stack │ │ Stack │ │ │ └─────────┘ └─────────┘ │ │ │ │ ┌─────────────────────────────┐ │ │ │ Shared Heap │ │ │ └─────────────────────────────┘ │ │ │ │ ┌─────────────────────────────┐ │ │ │ Global Data │ │ │ └─────────────────────────────┘ │ │ │ │ ┌─────────────────────────────┐ │ │ │ Code │ │ │ └─────────────────────────────┘ │ └─────────────────────────────────────┘
Why Use Multithreading?
- Performance: Utilize multiple CPU cores for parallel processing
- Responsiveness: Keep UI responsive while performing background tasks
- Resource Sharing: Threads share memory, reducing overhead compared to processes
- Economy: Creating threads is cheaper than creating processes
- Scalability: Applications can scale with available CPU cores
The POSIX Threads (pthreads) Library
The pthreads library is the standard threading interface for Unix-like systems, including Linux and macOS. For Windows, you can use the Windows API or cross-platform libraries.
#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h>
Compiling with pthreads:
gcc -pthread -o program program.c # or gcc -lpthread -o program program.c
Creating and Joining Threads
Basic Thread Creation:
#include <pthread.h> #include <stdio.h> #include <stdlib.h> // Thread function must return void* and take void* argument void* thread_function(void* arg) { int thread_num = *(int*)arg; printf("Thread %d: Hello from thread!\n", thread_num); // Return some value (optional) return (void*)(long)(thread_num * 10); } int main() { pthread_t thread1, thread2; int arg1 = 1, arg2 = 2; void* retval1, *retval2; // Create threads printf("Main: Creating threads...\n"); if (pthread_create(&thread1, NULL, thread_function, &arg1) != 0) { perror("Failed to create thread 1"); exit(1); } if (pthread_create(&thread2, NULL, thread_function, &arg2) != 0) { perror("Failed to create thread 2"); exit(1); } printf("Main: Threads created. Waiting for them to finish...\n"); // Wait for threads to complete pthread_join(thread1, &retval1); pthread_join(thread2, &retval2); printf("Main: Threads finished.\n"); printf("Thread 1 returned: %ld\n", (long)retval1); printf("Thread 2 returned: %ld\n", (long)retval2); return 0; } Thread Attributes
You can customize thread behavior using thread attributes:
#include <pthread.h> #include <stdio.h> void* thread_func(void* arg) { printf("Thread running with custom attributes\n"); return NULL; } int main() { pthread_t thread; pthread_attr_t attr; // Initialize attribute object pthread_attr_init(&attr); // Set detached state (thread will clean up itself when done) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // Set stack size (optional) pthread_attr_setstacksize(&attr, 1024 * 1024); // 1 MB stack // Create thread with attributes if (pthread_create(&thread, &attr, thread_func, NULL) != 0) { perror("Failed to create thread"); return 1; } // Destroy attribute object (no longer needed) pthread_attr_destroy(&attr); // Since thread is detached, we don't need to join printf("Main: Thread created in detached state\n"); // Give thread time to run sleep(1); return 0; } Thread Synchronization
1. Mutexes (Mutual Exclusion)
Mutexes protect shared data from concurrent access:
#include <pthread.h> #include <stdio.h> #include <stdlib.h> #define NUM_THREADS 5 #define NUM_INCREMENTS 1000000 // Shared counter long long counter = 0; // Mutex for protecting counter pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER; void* increment_counter(void* arg) { int thread_id = *(int*)arg; for (int i = 0; i < NUM_INCREMENTS; i++) { // Lock mutex before accessing shared counter pthread_mutex_lock(&counter_mutex); counter++; pthread_mutex_unlock(&counter_mutex); } printf("Thread %d finished\n", thread_id); return NULL; } int main() { pthread_t threads[NUM_THREADS]; int thread_ids[NUM_THREADS]; // 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("Failed to create thread"); exit(1); } } // Wait for all threads to finish for (int i = 0; i < NUM_THREADS; i++) { pthread_join(threads[i], NULL); } // Expected: NUM_THREADS * NUM_INCREMENTS printf("Final counter value: %lld\n", counter); printf("Expected value: %d\n", NUM_THREADS * NUM_INCREMENTS); // Destroy mutex pthread_mutex_destroy(&counter_mutex); return 0; } 2. Mutex with Error Checking
#include <pthread.h> #include <stdio.h> #include <errno.h> void demonstrate_mutex_errors() { pthread_mutex_t mutex; pthread_mutexattr_t attr; // Initialize mutex with error checking pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); pthread_mutex_init(&mutex, &attr); // First lock - should succeed int ret = pthread_mutex_lock(&mutex); printf("First lock: %s\n", ret == 0 ? "success" : "failed"); // Second lock by same thread - would deadlock with normal mutex, // but with error checking it returns EDEADLK ret = pthread_mutex_lock(&mutex); if (ret == EDEADLK) { printf("Second lock detected deadlock (as expected)\n"); } // Unlock once pthread_mutex_unlock(&mutex); // Try to unlock without holding lock ret = pthread_mutex_unlock(&mutex); if (ret == EPERM) { printf("Unlock without lock detected (as expected)\n"); } pthread_mutex_destroy(&mutex); pthread_mutexattr_destroy(&attr); } 3. Condition Variables
Condition variables allow threads to wait for certain conditions:
#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> // Shared data int buffer = 0; int data_ready = 0; // Synchronization primitives pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // Producer thread void* producer(void* arg) { int produced = 0; while (produced < 5) { // Simulate work sleep(1); pthread_mutex_lock(&mutex); // Produce data buffer = rand() % 100; printf("Producer: Produced %d\n", buffer); // Signal that data is ready data_ready = 1; pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex); produced++; } return NULL; } // Consumer thread void* consumer(void* arg) { int consumed = 0; while (consumed < 5) { pthread_mutex_lock(&mutex); // Wait for data to be ready while (!data_ready) { // Wait unlocks mutex and blocks, then reacquires when signaled pthread_cond_wait(&cond, &mutex); } // Consume data printf("Consumer: Consumed %d\n", buffer); data_ready = 0; pthread_mutex_unlock(&mutex); consumed++; } return NULL; } int main() { pthread_t prod, cons; // Create threads pthread_create(&prod, NULL, producer, NULL); pthread_create(&cons, NULL, consumer, NULL); // Wait for completion pthread_join(prod, NULL); pthread_join(cons, NULL); // Cleanup pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); return 0; } 4. Broadcast vs. Signal
#include <pthread.h> #include <stdio.h> #include <unistd.h> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; int condition_met = 0; void* waiter(void* arg) { int id = *(int*)arg; pthread_mutex_lock(&mutex); while (!condition_met) { printf("Waiter %d: Waiting...\n", id); pthread_cond_wait(&cond, &mutex); } printf("Waiter %d: Condition met! Proceeding.\n", id); pthread_mutex_unlock(&mutex); return NULL; } void* signaler(void* arg) { sleep(2); pthread_mutex_lock(&mutex); condition_met = 1; // Try both signal and broadcast // pthread_cond_signal(&cond); // Wakes only one thread pthread_cond_broadcast(&cond); // Wakes all waiting threads printf("Signaler: Condition signaled\n"); pthread_mutex_unlock(&mutex); return NULL; } int main() { pthread_t waiters[3]; pthread_t sig; int ids[3] = {1, 2, 3}; // Create waiting threads for (int i = 0; i < 3; i++) { pthread_create(&waiters[i], NULL, waiter, &ids[i]); } // Create signaler thread pthread_create(&sig, NULL, signaler, NULL); // Wait for all threads for (int i = 0; i < 3; i++) { pthread_join(waiters[i], NULL); } pthread_join(sig, NULL); pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); return 0; } Thread-Safe Data Structures
Thread-Safe Queue Example:
#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> typedef struct Node { int data; struct Node* next; } Node; typedef struct ThreadSafeQueue { Node* head; Node* tail; int size; pthread_mutex_t mutex; pthread_cond_t not_empty; pthread_cond_t not_full; int max_size; } ThreadSafeQueue; // Initialize queue void queue_init(ThreadSafeQueue* queue, int max_size) { queue->head = NULL; queue->tail = NULL; queue->size = 0; queue->max_size = max_size; pthread_mutex_init(&queue->mutex, NULL); pthread_cond_init(&queue->not_empty, NULL); pthread_cond_init(&queue->not_full, NULL); } // Destroy queue void queue_destroy(ThreadSafeQueue* queue) { pthread_mutex_lock(&queue->mutex); // Free all nodes Node* current = queue->head; while (current != NULL) { Node* temp = current; current = current->next; free(temp); } pthread_mutex_unlock(&queue->mutex); pthread_mutex_destroy(&queue->mutex); pthread_cond_destroy(&queue->not_empty); pthread_cond_destroy(&queue->not_full); } // Enqueue (producer) void queue_enqueue(ThreadSafeQueue* queue, int value) { pthread_mutex_lock(&queue->mutex); // Wait if queue is full while (queue->size >= queue->max_size) { pthread_cond_wait(&queue->not_full, &queue->mutex); } // Create new node Node* new_node = (Node*)malloc(sizeof(Node)); new_node->data = value; new_node->next = NULL; // Add to queue if (queue->tail == NULL) { queue->head = new_node; queue->tail = new_node; } else { queue->tail->next = new_node; queue->tail = new_node; } queue->size++; printf("Enqueued: %d (queue size: %d)\n", value, queue->size); // Signal that queue is not empty pthread_cond_signal(&queue->not_empty); pthread_mutex_unlock(&queue->mutex); } // Dequeue (consumer) int queue_dequeue(ThreadSafeQueue* queue) { pthread_mutex_lock(&queue->mutex); // Wait if queue is empty while (queue->head == NULL) { pthread_cond_wait(&queue->not_empty, &queue->mutex); } // Remove from queue Node* temp = queue->head; int value = temp->data; queue->head = queue->head->next; if (queue->head == NULL) { queue->tail = NULL; } queue->size--; free(temp); printf("Dequeued: %d (queue size: %d)\n", value, queue->size); // Signal that queue is not full pthread_cond_signal(&queue->not_full); pthread_mutex_unlock(&queue->mutex); return value; } // Producer thread void* producer_thread(void* arg) { ThreadSafeQueue* queue = (ThreadSafeQueue*)arg; for (int i = 0; i < 10; i++) { queue_enqueue(queue, i); usleep(rand() % 500000); // Random delay } return NULL; } // Consumer thread void* consumer_thread(void* arg) { ThreadSafeQueue* queue = (ThreadSafeQueue*)arg; for (int i = 0; i < 10; i++) { int value = queue_dequeue(queue); usleep(rand() % 500000); // Random delay } return NULL; } int main() { ThreadSafeQueue queue; queue_init(&queue, 5); // Max size 5 pthread_t producer, consumer; // Create producer and consumer pthread_create(&producer, NULL, producer_thread, &queue); pthread_create(&consumer, NULL, consumer_thread, &queue); // Wait for completion pthread_join(producer, NULL); pthread_join(consumer, NULL); // Cleanup queue_destroy(&queue); return 0; } Thread-Local Storage (TLS)
Each thread can have its own private data:
#include <pthread.h> #include <stdio.h> #include <stdlib.h> // Thread-local storage using __thread keyword (GCC) __thread int thread_local_counter = 0; // Alternative: pthread keys pthread_key_t thread_key; void destructor(void* value) { free(value); printf("Thread-local data freed\n"); } void* thread_function(void* arg) { int thread_id = *(int*)arg; // Using __thread variable thread_local_counter = thread_id * 100; printf("Thread %d: __thread counter = %d\n", thread_id, thread_local_counter); // Using pthread_key int* data = (int*)malloc(sizeof(int)); *data = thread_id * 1000; pthread_setspecific(thread_key, data); int* retrieved = (int*)pthread_getspecific(thread_key); printf("Thread %d: pthread_key data = %d\n", thread_id, *retrieved); return NULL; } int main() { pthread_t threads[3]; int ids[3] = {1, 2, 3}; // Create thread-local key pthread_key_create(&thread_key, destructor); // Create threads for (int i = 0; i < 3; i++) { pthread_create(&threads[i], NULL, thread_function, &ids[i]); } // Wait for threads for (int i = 0; i < 3; i++) { pthread_join(threads[i], NULL); } // Cleanup pthread_key_delete(thread_key); return 0; } Thread Pools
A thread pool manages a group of worker threads to execute tasks:
#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> typedef struct Task { void (*function)(void*); void* argument; struct Task* next; } Task; typedef struct ThreadPool { pthread_t* threads; int thread_count; Task* task_queue_head; Task* task_queue_tail; int queue_size; pthread_mutex_t queue_mutex; pthread_cond_t queue_not_empty; pthread_cond_t queue_not_full; int shutdown; int max_queue_size; } ThreadPool; // Worker thread function void* worker_thread(void* arg) { ThreadPool* pool = (ThreadPool*)arg; while (1) { pthread_mutex_lock(&pool->queue_mutex); // Wait for task or shutdown while (pool->task_queue_head == NULL && !pool->shutdown) { pthread_cond_wait(&pool->queue_not_empty, &pool->queue_mutex); } if (pool->shutdown && pool->task_queue_head == NULL) { pthread_mutex_unlock(&pool->queue_mutex); break; } // Get task from queue Task* task = pool->task_queue_head; pool->task_queue_head = pool->task_queue_head->next; if (pool->task_queue_head == NULL) { pool->task_queue_tail = NULL; } pool->queue_size--; // Signal that queue is not full pthread_cond_signal(&pool->queue_not_full); pthread_mutex_unlock(&pool->queue_mutex); // Execute task task->function(task->argument); free(task); } return NULL; } // Initialize thread pool ThreadPool* thread_pool_create(int thread_count, int max_queue_size) { ThreadPool* pool = (ThreadPool*)malloc(sizeof(ThreadPool)); pool->thread_count = thread_count; pool->threads = (pthread_t*)malloc(thread_count * sizeof(pthread_t)); pool->task_queue_head = NULL; pool->task_queue_tail = NULL; pool->queue_size = 0; pool->max_queue_size = max_queue_size; pool->shutdown = 0; pthread_mutex_init(&pool->queue_mutex, NULL); pthread_cond_init(&pool->queue_not_empty, NULL); pthread_cond_init(&pool->queue_not_full, NULL); // Create worker threads for (int i = 0; i < thread_count; i++) { pthread_create(&pool->threads[i], NULL, worker_thread, pool); } return pool; } // Add task to pool int thread_pool_add_task(ThreadPool* pool, void (*function)(void*), void* argument) { pthread_mutex_lock(&pool->queue_mutex); // Wait if queue is full while (pool->queue_size >= pool->max_queue_size && !pool->shutdown) { pthread_cond_wait(&pool->queue_not_full, &pool->queue_mutex); } if (pool->shutdown) { pthread_mutex_unlock(&pool->queue_mutex); return -1; } // Create task Task* task = (Task*)malloc(sizeof(Task)); task->function = function; task->argument = argument; task->next = NULL; // Add to queue if (pool->task_queue_tail == NULL) { pool->task_queue_head = task; pool->task_queue_tail = task; } else { pool->task_queue_tail->next = task; pool->task_queue_tail = task; } pool->queue_size++; // Signal that queue is not empty pthread_cond_signal(&pool->queue_not_empty); pthread_mutex_unlock(&pool->queue_mutex); return 0; } // Shutdown thread pool void thread_pool_shutdown(ThreadPool* pool) { pthread_mutex_lock(&pool->queue_mutex); pool->shutdown = 1; // Wake up all workers pthread_cond_broadcast(&pool->queue_not_empty); pthread_mutex_unlock(&pool->queue_mutex); // Wait for all workers to finish for (int i = 0; i < pool->thread_count; i++) { pthread_join(pool->threads[i], NULL); } // Clean up remaining tasks pthread_mutex_lock(&pool->queue_mutex); Task* current = pool->task_queue_head; while (current != NULL) { Task* temp = current; current = current->next; free(temp); } pthread_mutex_unlock(&pool->queue_mutex); // Cleanup pthread_mutex_destroy(&pool->queue_mutex); pthread_cond_destroy(&pool->queue_not_empty); pthread_cond_destroy(&pool->queue_not_full); free(pool->threads); free(pool); } // Example task function void print_number(void* arg) { int num = *(int*)arg; printf("Task: %d (thread %lu)\n", num, pthread_self()); free(arg); // Free allocated argument usleep(100000); // Simulate work } int main() { // Create thread pool with 4 threads and max queue size 10 ThreadPool* pool = thread_pool_create(4, 10); // Add 20 tasks for (int i = 0; i < 20; i++) { int* num = (int*)malloc(sizeof(int)); *num = i; thread_pool_add_task(pool, print_number, num); } // Give tasks time to complete sleep(3); // Shutdown pool thread_pool_shutdown(pool); return 0; } Common Pitfalls and Best Practices
1. Deadlock Prevention
#include <pthread.h> #include <stdio.h> // Example of deadlock and prevention pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; // BAD: Can deadlock void* bad_worker1(void* arg) { pthread_mutex_lock(&mutex1); printf("Worker1: locked mutex1\n"); sleep(1); pthread_mutex_lock(&mutex2); // May deadlock if worker2 has mutex2 printf("Worker1: locked mutex2\n"); pthread_mutex_unlock(&mutex2); pthread_mutex_unlock(&mutex1); return NULL; } void* bad_worker2(void* arg) { pthread_mutex_lock(&mutex2); printf("Worker2: locked mutex2\n"); sleep(1); pthread_mutex_lock(&mutex1); // May deadlock if worker1 has mutex1 printf("Worker2: locked mutex1\n"); pthread_mutex_unlock(&mutex1); pthread_mutex_unlock(&mutex2); return NULL; } // GOOD: Consistent lock ordering prevents deadlock void* good_worker1(void* arg) { pthread_mutex_lock(&mutex1); pthread_mutex_lock(&mutex2); printf("Worker1: locked both mutexes in order\n"); pthread_mutex_unlock(&mutex2); pthread_mutex_unlock(&mutex1); return NULL; } void* good_worker2(void* arg) { // Same locking order as worker1 pthread_mutex_lock(&mutex1); pthread_mutex_lock(&mutex2); printf("Worker2: locked both mutexes in order\n"); pthread_mutex_unlock(&mutex2); pthread_mutex_unlock(&mutex1); return NULL; } 2. Trylock to Avoid Deadlock
#include <pthread.h> #include <stdio.h> #include <unistd.h> pthread_mutex_t mutex_a = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mutex_b = PTHREAD_MUTEX_INITIALIZER; void* trylock_worker(void* arg) { int id = *(int*)arg; while (1) { // Try to lock first mutex pthread_mutex_lock(&mutex_a); // Try to lock second mutex without blocking if (pthread_mutex_trylock(&mutex_b) == 0) { printf("Thread %d: Got both locks\n", id); // Do work sleep(1); pthread_mutex_unlock(&mutex_b); pthread_mutex_unlock(&mutex_a); break; } else { // Couldn't get second lock, release first and try again pthread_mutex_unlock(&mutex_a); printf("Thread %d: Couldn't get both locks, retrying\n", id); usleep(rand() % 100000); // Random backoff } } return NULL; } 3. Thread Safety Rules
#include <pthread.h> #include <stdio.h> #include <string.h> // Thread-safe function with local data char* thread_safe_strerror(int errnum) { static __thread char buffer[256]; // Thread-local storage // Actual error string generation snprintf(buffer, sizeof(buffer), "Error %d", errnum); return buffer; } // Thread-safe counter typedef struct { pthread_mutex_t mutex; long long value; } AtomicCounter; void atomic_increment(AtomicCounter* counter) { pthread_mutex_lock(&counter->mutex); counter->value++; pthread_mutex_unlock(&counter->mutex); } long long atomic_get(AtomicCounter* counter) { pthread_mutex_lock(&counter->mutex); long long val = counter->value; pthread_mutex_unlock(&counter->mutex); return val; } Performance Considerations
#include <pthread.h> #include <stdio.h> #include <time.h> // Measure thread creation overhead void measure_thread_overhead() { struct timespec start, end; pthread_t thread; clock_gettime(CLOCK_MONOTONIC, &start); for (int i = 0; i < 1000; i++) { pthread_create(&thread, NULL, thread_function, NULL); pthread_join(thread, NULL); } clock_gettime(CLOCK_MONOTONIC, &end); double elapsed = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9; printf("Average thread creation+join time: %.3f ms\n", elapsed * 1000 / 1000); } // False sharing demonstration typedef struct { int counter; // Multiple counters in same cache line int counter2; // can cause false sharing } BadCounter; typedef struct { int counter; char padding[64]; // Pad to separate cache lines int counter2; } GoodCounter; Complete Example: Parallel Matrix Multiplication
#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <time.h> #define SIZE 1000 #define NUM_THREADS 4 typedef struct { int thread_id; int start_row; int end_row; int (*A)[SIZE]; int (*B)[SIZE]; int (*C)[SIZE]; } ThreadData; // Matrix multiplication for a range of rows void* multiply_rows(void* arg) { ThreadData* data = (ThreadData*)arg; printf("Thread %d: processing rows %d to %d\n", data->thread_id, data->start_row, data->end_row - 1); for (int i = data->start_row; i < data->end_row; i++) { for (int j = 0; j < SIZE; j++) { int sum = 0; for (int k = 0; k < SIZE; k++) { sum += data->A[i][k] * data->B[k][j]; } data->C[i][j] = sum; } } return NULL; } int main() { // Allocate matrices int (*A)[SIZE] = malloc(SIZE * sizeof(*A)); int (*B)[SIZE] = malloc(SIZE * sizeof(*B)); int (*C)[SIZE] = malloc(SIZE * sizeof(*C)); if (!A || !B || !C) { perror("Failed to allocate matrices"); return 1; } // Initialize matrices srand(time(NULL)); for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) { A[i][j] = rand() % 10; B[i][j] = rand() % 10; } } // Create thread data pthread_t threads[NUM_THREADS]; ThreadData thread_data[NUM_THREADS]; int rows_per_thread = SIZE / NUM_THREADS; struct timespec start, end; clock_gettime(CLOCK_MONOTONIC, &start); // Create threads for (int i = 0; i < NUM_THREADS; i++) { thread_data[i].thread_id = i; thread_data[i].start_row = i * rows_per_thread; thread_data[i].end_row = (i == NUM_THREADS - 1) ? SIZE : (i + 1) * rows_per_thread; thread_data[i].A = A; thread_data[i].B = B; thread_data[i].C = C; pthread_create(&threads[i], NULL, multiply_rows, &thread_data[i]); } // Wait for all threads 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; printf("Matrix multiplication completed in %.3f seconds\n", elapsed); printf("Result check (C[0][0] = %d)\n", C[0][0]); // Cleanup free(A); free(B); free(C); return 0; } Debugging Multithreaded Programs
#include <pthread.h> #include <stdio.h> #include <signal.h> // Thread sanitizer (compile with -fsanitize=thread) // gcc -fsanitize=thread -g -pthread program.c // Helgrind (Valgrind tool) // valgrind --tool=helgrind ./program // GDB debugging with threads void gdb_thread_commands() { printf("GDB commands for threads:\n"); printf(" info threads - List all threads\n"); printf(" thread <num> - Switch to thread <num>\n"); printf(" thread apply all bt - Backtrace all threads\n"); printf(" set scheduler-locking on - Prevent thread switching\n"); } // Signal handling in multithreaded programs pthread_mutex_t signal_mutex = PTHREAD_MUTEX_INITIALIZER; void signal_handler(int sig) { // Only async-signal-safe functions here write(STDERR_FILENO, "Signal received\n", 16); } void setup_signal_handling() { struct sigaction sa; sa.sa_handler = signal_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGINT, &sa, NULL); } Summary: pthread Functions Reference
| Function | Purpose |
|---|---|
pthread_create() | Create a new thread |
pthread_join() | Wait for thread termination |
pthread_detach() | Make thread detached (auto-cleanup) |
pthread_self() | Get current thread ID |
pthread_equal() | Compare thread IDs |
pthread_exit() | Exit current thread |
pthread_mutex_init() | Initialize mutex |
pthread_mutex_lock() | Lock mutex |
pthread_mutex_unlock() | Unlock mutex |
pthread_mutex_trylock() | Attempt to lock mutex |
pthread_mutex_destroy() | Destroy mutex |
pthread_cond_init() | Initialize condition variable |
pthread_cond_wait() | Wait on condition |
pthread_cond_signal() | Signal one waiting thread |
pthread_cond_broadcast() | Signal all waiting threads |
pthread_cond_destroy() | Destroy condition variable |
pthread_key_create() | Create thread-local storage key |
pthread_setspecific() | Set thread-local value |
pthread_getspecific() | Get thread-local value |
pthread_key_delete() | Delete thread-local key |
Conclusion
Multithreading in C using pthreads provides a powerful foundation for building concurrent applications. By understanding thread creation, synchronization mechanisms (mutexes, condition variables), and thread-safe data structures, you can create efficient, scalable programs that fully utilize modern multi-core processors.
Remember these key principles:
- Always protect shared data with mutexes
- Avoid deadlocks by establishing consistent lock ordering
- Use condition variables for thread coordination
- Be aware of thread safety in function design
- Profile and test thoroughly, as threading bugs can be subtle
With practice, these concepts become second nature, enabling you to harness the full power of concurrent programming in C.
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/
