Blog Datasheets Home About me Clients My work Services Contact

G2Labs Grzegorz Grzęda

Exploring Multithreading and Parallel Programming in C

July 14, 2024

Exploring Multithreading and Parallel Programming in C

Introduction

In today’s era of modern computing, where multi-core processors are becoming increasingly common, it is essential for developers to understand and harness the power of multithreading and parallel programming. These techniques allow us to execute multiple tasks concurrently, exploiting the full potential of our hardware. In this blog post, we will dive into the world of multithreading and parallel programming in C, examining its concepts and implementation.

Understanding Multithreading

Multithreading is a programming technique that enables concurrent execution of multiple tasks within a single application. It leverages the concept of threads, which are lightweight execution units within a process. By dividing a program into multiple threads, we can perform various operations in parallel, leading to significantly improved performance and responsiveness.

Creating Threads in C

Let’s take a look at how we can create threads in C using the standard POSIX Threads (Pthreads) library:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void *thread_function(void *arg) {
    int thread_arg = *(int *)arg;
    printf("Hello from thread %d\n", thread_arg);
    pthread_exit(NULL);
}

int main() {
    pthread_t thread;
    int thread_arg = 1;
    
    if (pthread_create(&thread, NULL, thread_function, &thread_arg) != 0) {
        fprintf(stderr, "Error creating thread\n");
        return 1;
    }
    
    pthread_join(thread, NULL);
    printf("Thread finished\n");

    return 0;
}

In this example, we create a thread by calling pthread_create, passing the thread_function and a pointer to the argument thread_arg. The thread_function is executed concurrently in the new thread, and once it completes, we join the thread using pthread_join.

Synchronization and Mutexes

When multiple threads access shared resources concurrently, the program’s correctness may be compromised due to data races and other synchronization issues. To prevent these problems, we can use mutexes, short for “mutual exclusion objects,” which enforce exclusive access to shared data.

Consider the following example, where we have two threads incrementing a shared counter:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

int counter = 0;
pthread_mutex_t counter_mutex;

void *thread_function(void *arg) {
    pthread_mutex_lock(&counter_mutex);
    counter++;
    pthread_mutex_unlock(&counter_mutex);

    pthread_exit(NULL);
}

int main() {
    pthread_t thread1, thread2;
    
    pthread_mutex_init(&counter_mutex, NULL);

    pthread_create(&thread1, NULL, thread_function, NULL);
    pthread_create(&thread2, NULL, thread_function, NULL);

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    
    pthread_mutex_destroy(&counter_mutex);

    printf("Counter value: %d\n", counter);

    return 0;
}

In this example, we use pthread_mutex_lock to acquire the lock on the mutex, increment the shared counter, and then release the lock using pthread_mutex_unlock. By ensuring only one thread can access the counter at a time, we avoid data races and maintain consistency.

Parallel Programming with OpenMP

In addition to pthreads, C also provides OpenMP (Open Multi-Processing) for parallel programming. OpenMP is a widely adopted API for shared memory multiprocessing, enabling automatic parallelization of loops and parallel regions.

Consider the following example, where we parallelize a loop using OpenMP:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stdlib.h>
#include <omp.h>

int main() {
    int num_threads = omp_get_max_threads();
    int i, sum = 0;
    
    #pragma omp parallel for reduction(+:sum)
    for(i = 0; i < 1000; i++) {
        sum += i;
    }

    printf("Sum: %d\n", sum);

    return 0;
}

In this example, we use #pragma omp parallel for to indicate that the following loop should be executed in parallel. The reduction(+:sum) clause ensures that each thread has a private copy of sum and performs a reduction operation to get the final sum.

Conclusion

Multithreading and parallel programming are vital techniques for leveraging the computing power of modern systems. In this blog post, we explored the basics of multithreading in C using Pthreads and synchronization with mutexes. We also touched on parallel programming using OpenMP. With these tools and concepts, you can now start exploring and harnessing the benefits of concurrency in your C programs.

Remember, mastering multithreading and parallel programming takes time and practice. So keep experimenting, learning, and pushing your limits to write efficient and scalable code.

Happy coding!


➡️ Getting Started with ATmega-328 Development


⬅️ Introduction to ATmega-328 Microcontroller


Go back to Posts.