google-site-verification: google61fe8ba583a51912.html
Mastering Concurrency: A Comprehensive Guide to Multithreading Basics in C

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?

  1. Performance: Utilize multiple CPU cores for parallel processing
  2. Responsiveness: Keep UI responsive while performing background tasks
  3. Resource Sharing: Threads share memory, reducing overhead compared to processes
  4. Economy: Creating threads is cheaper than creating processes
  5. 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

FunctionPurpose
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/

Leave a Reply

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


Macro Nepal Helper