Logo

dev-resources.site

for different kinds of informations.

Mastering Multithreading in C Programming: A Deep Dive with In-Depth Explanations and Advanced Concepts

Published at
7/8/2024
Categories
multithreading
posix
c
embedded
Author
vivekyadav200988
Categories
4 categories in total
multithreading
open
posix
open
c
open
embedded
open
Author
16 person written this
vivekyadav200988
open
Mastering Multithreading in C Programming: A Deep Dive with In-Depth Explanations and Advanced Concepts

Introduction:

Multithreading in C programming enables developers to harness the full potential of modern multicore processors, facilitating concurrent execution of tasks within a single process. This comprehensive guide explores fundamental multithreading concepts, synchronization mechanisms, and advanced topics, providing detailed explanations and sample code for each concept.

1. Understanding Threads:

Threads are independent sequences of execution within a process, allowing for concurrent execution of tasks. Understanding thread creation, management, and states is crucial for effective multithreading.

Thread Creation:
pthread_create(): Initializes a new thread and starts its execution.
pthread_join(): Waits for a thread to terminate before proceeding.

#include <stdio.h>
#include <pthread.h>

void *threadFunc(void *arg) {
    printf("Hello from the new thread!\n");
    pthread_exit(NULL);
}

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, threadFunc, NULL);
    pthread_join(tid, NULL);
    printf("Back to the main thread.\n");
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

2. Synchronization and Mutual Exclusion:

Race conditions occur when multiple threads access shared resources concurrently, leading to unpredictable behavior. Synchronization mechanisms such as mutexes, semaphores, and condition variables ensure thread safety.

Mutexes (Mutual Exclusion):
Mutexes provide mutual exclusion, allowing only one thread to access a shared resource at a time. They prevent data corruption and ensure consistent behavior.

#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int sharedVariable = 0;

void *threadFunc(void *arg) {
    pthread_mutex_lock(&mutex);
    sharedVariable++;
    printf("Thread incremented sharedVariable to: %d\n", sharedVariable);
    pthread_mutex_unlock(&mutex);
    pthread_exit(NULL);
    }

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, threadFunc, NULL);
    pthread_mutex_lock(&mutex);
    sharedVariable--;
    printf("Main thread decremented sharedVariable to: %d\n", sharedVariable);
    pthread_mutex_unlock(&mutex);
    pthread_join(tid, NULL);
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

Semaphores:
Semaphores are synchronization primitives used to control access to shared resources and coordinate the execution of multiple threads. They maintain a count to limit the number of threads accessing the resource simultaneously.

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

sem_t semaphore;

void *threadFunc(void *arg) {
    sem_wait(&semaphore);
    printf("Thread acquired semaphore\n");
    // Critical section
    sem_post(&semaphore);
    pthread_exit(NULL);
}

int main() {
    pthread_t tid;
    sem_init(&semaphore, 0, 1); // Initialize semaphore with value 1
    pthread_create(&tid, NULL, threadFunc, NULL);
    // Main thread
    sem_wait(&semaphore);
    printf("Main thread acquired semaphore\n");
    // Critical section
    sem_post(&semaphore);
    pthread_join(tid, NULL);
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

3. Thread Communication:

Thread communication facilitates coordination and synchronization between threads. Condition variables allow threads to wait for specific conditions to be met.

Condition Variables:
Condition variables enable threads to wait for a specific condition to occur. They are commonly used in producer-consumer scenarios, where a thread waits for data availability before proceeding.

#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t condVar = PTHREAD_COND_INITIALIZER;
int dataReady = 0;

void *producer(void *arg) {
    pthread_mutex_lock(&mutex);
    dataReady = 1;
    pthread_cond_signal(&condVar);
    pthread_mutex_unlock(&mutex);
    pthread_exit(NULL);
}

void *consumer(void *arg) {
    pthread_mutex_lock(&mutex);
    while (!dataReady) {
        pthread_cond_wait(&condVar, &mutex);
    }
    printf("Consumer: Data is ready!\n");
    pthread_mutex_unlock(&mutex);
    pthread_exit(NULL);
}

int main() {
    pthread_t producerThread, consumerThread;
    pthread_create(&producerThread, NULL, producer, NULL);
    pthread_create(&consumerThread, NULL, consumer, NULL);
    pthread_join(producerThread, NULL);
    pthread_join(consumerThread, NULL);
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

4. Advanced Concepts:

Advanced topics such as priority inversion, starvation, deadlock, and spinlock are critical for building robust multithreaded applications.

Priority Inversion:
Priority inversion occurs when a low-priority thread holds a resource required by a high-priority thread, causing priority inversion. Priority inheritance protocol helps mitigate this issue by temporarily raising the priority of the low-priority thread to that of the high-priority thread.

#include <stdio.h>
#include <pthread.h>

pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;

void *highPriorityThread(void *arg) {
    pthread_mutex_lock(&mutex1);
    pthread_mutex_lock(&mutex2);
    // Perform high-priority task
    pthread_mutex_unlock(&mutex2);
    pthread_mutex_unlock(&mutex1);
    pthread_exit(NULL);
}

void *lowPriorityThread(void *arg) {
    pthread_mutex_lock(&mutex2);
    pthread_mutex_lock(&mutex1);
    // Perform low-priority task
    pthread_mutex_unlock(&mutex1);
    pthread_mutex_unlock(&mutex2);
    pthread_exit(NULL);
}

int main() {
    pthread_t highPrioTid, lowPrioTid;
    pthread_create(&highPrioTid, NULL, highPriorityThread, NULL);
    pthread_create(&lowPrioTid, NULL, lowPriorityThread, NULL);
    pthread_join(highPrioTid, NULL);
    pthread_join(lowPrioTid, NULL);
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

Starvation:
Starvation occurs when a thread is unable to gain access to required resources due to other threads continuously acquiring those resources. Fair scheduling policies ensure that all threads have a fair chance of resource allocation, preventing starvation.

#include <stdio.h>
#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int sharedResource = 0;

void *threadFunc(void *arg) {
    pthread_mutex_lock(&mutex);
    // Increment shared resource
    sharedResource++;
    printf("Thread incremented sharedResource to: %d\n", sharedResource);
    pthread_mutex_unlock(&mutex);
    pthread_exit(NULL);
}

int main() {
    pthread_t tid1, tid2;

    // Create two threads
    pthread_create(&tid1, NULL, threadFunc, NULL);
    pthread_create(&tid2, NULL, threadFunc, NULL);

    // Wait for both threads to finish
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    // Main thread
    pthread_mutex_lock(&mutex);
    // Access shared resource
    printf("Main thread accessed sharedResource: %d\n", sharedResource);
    pthread_mutex_unlock(&mutex);

    return 0;
}
Enter fullscreen mode Exit fullscreen mode

Deadlock:
Deadlock occurs when two or more threads are waiting indefinitely for each other to release resources they need. Avoiding circular wait and implementing deadlock detection and recovery mechanisms help mitigate deadlock situations.

#include <stdio.h>
#include <pthread.h>

pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;

void *thread1(void *arg) {
    pthread_mutex_lock(&mutex1);
    pthread_mutex_lock(&mutex2);
    // Critical section
    pthread_mutex_unlock(&mutex2);
    pthread_mutex_unlock(&mutex1);
    pthread_exit(NULL);
}

void *thread2(void *arg) {
    pthread_mutex_lock(&mutex2);
    pthread_mutex_lock(&mutex1);  // Potential deadlock point
    // Critical section
    pthread_mutex_unlock(&mutex1);
    pthread_mutex_unlock(&mutex2);
    pthread_exit(NULL);
}

int main() {
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, thread1, NULL);
    pthread_create(&tid2, NULL, thread2, NULL);
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

Spinlock:
Spinlocks are synchronization primitives where a thread continuously polls for the availability of a resource. They are efficient for short critical sections and low contention scenarios.

#include <stdio.h>
#include <pthread.h>

pthread_spinlock_t spinlock;

void *threadFunc(void *arg) {
    pthread_spin_lock(&spinlock);
    // Critical section
    printf("Thread acquired spinlock\n");
    // Perform some task
    pthread_spin_unlock(&spinlock);
    pthread_exit(NULL);
}

int main() {
    pthread_t tid1, tid2;
    pthread_spin_init(&spinlock, 0);
    pthread_create(&tid1, NULL, threadFunc, NULL);
    pthread_create(&tid2, NULL, threadFunc, NULL);
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_spin_destroy(&spinlock);
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

Conclusion:

Mastering multithreading in C programming requires a deep understanding of fundamental concepts, synchronization mechanisms, and advanced topics. By delving into these concepts and exploring sample code, developers can build robust, efficient, and responsive multithreaded applications. Continuous practice, experimentation, and adherence to best practices are key to becoming proficient in multithreading and developing reliable software systems that fully utilize the capabilities of modern hardware.

multithreading Article's
30 articles in total
Favicon
Python 3.13: The Gateway to High-Performance Multithreading Without GIL
Favicon
# Boost Your Python Tasks with `ThreadPoolExecutor`
Favicon
ReentrantReadWriteLock
Favicon
ReentrantLock in Java
Favicon
Synchronizing Threads with Semaphores: Practicing Concurrency in Java - LeetCode Problem 1115, "Print FooBar Alternately"
Favicon
Effective Ways to Use Locks in Kotlin
Favicon
Python Multithreading and Multiprocessing
Favicon
Introducing Robogator for PS and C#
Favicon
Multithreading Concepts Part 3 : Deadlock
Favicon
Multithreading Concepts Part 2 : Starvation
Favicon
Parallelism, Asynchronization, Multi-threading, & Multi-processing
Favicon
Using WebSocket with Python
Favicon
Power of Java Virtual Threads: A Deep Dive into Scalable Concurrency
Favicon
GIL "removal" for Python true multi threading
Favicon
Optimizing Your Development Machine: How Many Cores and Threads Do You Need for Programming?
Favicon
Multithreading Concepts Part 1: Atomicity and Immutability
Favicon
The Benefits of Having More Threads than Cores: Unlocking the Power of Multi-threading in Modern Computing
Favicon
Mastering Java Collections with Multithreading: Best Practices and Practical Examples
Favicon
Understanding Multithreading: Inner Workings and Key Concepts
Favicon
Handling Concurrency in C#: A Guide to Multithreading and Parallelism
Favicon
MultiThreading vs MultiProcessing
Favicon
Achieving multi-threading by creating threads manually in Swift
Favicon
Multithreading in Java : A Deep Dive
Favicon
Understanding std::unique_lock and std::shared_lock in C++
Favicon
Swift Concurrency
Favicon
Mastering Multithreading in C Programming: A Deep Dive with In-Depth Explanations and Advanced Concepts
Favicon
Understanding Multithreading in Python
Favicon
Introduction to Monitor Class in C#
Favicon
Deep Dive Into Race Condition Problem inΒ .NET
Favicon
Goroutines: Solving the problem of efficient multithreading

Featured ones: