Welcome to the next lesson in our exploration of parallel and distributed systems. In our previous session, we distinguished between user-level and kernel-level threads, establishing that most modern systems, including those you'll program for, use kernel-level threads for their robustness and ability to leverage multi-core hardware. We concluded that this theoretical understanding provides the perfect foundation for writing our own concurrent code.
Today, we transition from theory to practice. You will learn to implement a basic multi-threaded program using POSIX Threads (pthreads), the standard C interface for thread management on Unix-like systems. We will build a program that performs a parallel array summation, covering the essential functions for creating threads, passing them data, synchronizing their completion, and aggregating their results. This hands-on experience is a crucial first step toward building complex parallel applications.
The POSIX Threads (pthreads) API
Before we write code, let's get acquainted with the tools. Pthreads is a library that provides a standardized API for creating and managing threads.
As a quick refresher from our previous discussions on processes and threads, remember that threads within the same process share memory, such as the data and heap segments. This is what makes them "lightweight" compared to processes. Each thread, however, has its own stack for local variables and its own set of CPU registers, including the program counter, allowing it to execute independently.

The core of the pthreads API for our purposes revolves around creating a thread, waiting for it to finish, and having it exit. The following diagram illustrates this fundamental lifecycle.

This lifecycle is primarily managed by two functions: pthread_create() and pthread_join(). The following video provides an excellent high-level introduction to these concepts.
Introduction To Threads (pthreads) | C Programming Tutorial
This tutorial from Portfolio Courses offers a clear and concise introduction to the basic mechanics of pthreads.
Watch these two segments: The introduction, which explains what threads are and introduces the pthread_create and pthread_join functions in the context of the thread lifecycle. The basic implementation, which shows how to set up a simple C program with the necessary header and variables to call these functions.
Now, let's look at the formal definitions and a basic example from a comprehensive pthreads tutorial.
[PDF] POSIX Threads Programming - IITKgp CSE
This document from IITKgp provides a detailed look at the pthreads API. We'll focus on the core thread management functions and a simple "Hello World" example.
First, read the section on thread creation. Pay close attention to the signature of pthread_create and the purpose of its four arguments. Next, read about terminating threads and the role of pthread_exit(). Note the important point about what happens if main() finishes before its threads. Finally, study the "Hello World" example code. This demonstrates the minimal structure of a pthreads program.
One crucial practical detail is how to compile a program that uses the pthreads library. You need to link against it, which on most systems is done with the -pthread flag.
[PDF] POSIX Threads Programming - IITKgp CSE
This small section shows the compiler commands needed for various platforms.
Review the table under the section Compiling Threaded Programs. For a standard Linux environment with GCC, the command is gcc -pthread your_program.c -o your_executable.
Passing Data and Retrieving Results
A thread is not very useful if it can't receive data to work on or return a result. The pthread_create function only allows a single void* argument, which requires some specific programming patterns to handle data transfer.
Passing Arguments to a Thread
You can pass a pointer to any data type by casting it to void*. For multiple arguments, the standard practice is to bundle them into a struct and pass a pointer to an instance of that struct.
The following video explains how to pass a single value, and the reading that follows shows the more general struct-based approach.
Introduction To Threads (pthreads) | C Programming Tutorial
This segment clearly demonstrates the mechanics of pointer casting to pass an argument.
Watch the section on passing arguments. Notice how the value is passed by reference, and the thread function must cast the void* back to the correct pointer type to dereference it.
[PDF] POSIX Threads Programming - IITKgp CSE
This section provides code examples for safely passing data, including the robust method of using a structure.
Read the section on passing arguments. Pay special attention to "Example 2", which uses a struct, and the warning in "Example 3" about the dangers of passing a pointer to a loop variable.
Synchronizing and Retrieving Results
The function pthread_join() serves two purposes:
- It blocks the calling thread (e.g.,
main) until the target thread terminates. This is a fundamental synchronization mechanism. - It allows the calling thread to receive the return value from the completed thread.
The following reading explains this mechanism and provides a complete example.
[PDF] POSIX Threads Programming - IITKgp CSE
This section covers pthread_join() in detail.
Read the subsections on joining and detaching. Then, carefully study the Pthread Joining Example. It demonstrates how main waits for all threads to finish their work before exiting.
Building a Parallel Summation Program
Now we will apply these concepts to our main goal: parallelizing the summation of an array of numbers. We will create two threads, have each sum up half of the array, and then combine their results in the main thread.
There are two common patterns for combining the results:
- Mutex-Protected Global Variable: Each thread adds its partial sum to a single global variable. This requires a mutex (a lock) to prevent a "race condition" where both threads try to update the variable simultaneously, leading to a corrupt result.
- Returning Partial Sums: Each thread computes its partial sum and returns the value to the main thread via
pthread_join. The main thread then adds the two partial sums to get the final result.
Let's explore both.
Method 1: Using a Global Variable and a Mutex
A mutex (short for mutual exclusion) is a synchronization primitive that ensures only one thread can access a shared resource at a time. A thread "locks" the mutex before accessing the shared data and "unlocks" it afterward.
The following code example implements the parallel sum using this approach.
Multithreading and Synchronization - Jyotiprakash's Blog
This blog post provides a complete, well-explained example of parallel summation using a mutex.
Carefully study the full program provided in the article, starting from the code block. Pay close attention to these key parts: The global totalSum variable and the pthread_mutex_t mutexSum. The sumRange function, which calculates a partial sum. The use of pthread_mutex_lock() and pthread_mutex_unlock() to protect the update to totalSum. The main function, which divides the work, creates the threads, and then joins them.
Method 2: Returning Partial Sums from Threads
This method avoids a shared global variable for the sum. Each thread works independently and communicates its result back to main. This often leads to cleaner code when the work can be so neatly partitioned. A challenge here is that a thread can only return a single value (a void*). To return our calculated sum, we must allocate memory on the heap for the result.
This next video provides a fantastic step-by-step walkthrough of this exact problem.
Practical example for using threads #1 (Summing numbers from an array)
This CodeVault video builds a parallel array summation program from scratch, focusing on returning values from threads.
Watch the core development process: Problem setup: The strategy of splitting the array and passing the start index to each thread. Thread function: Implementing the loop to calculate the partial sum. Returning the sum: This is the most crucial part. It explains how to use the argument pointer to return the calculated sum and how the main thread retrieves it via pthread_join. It also correctly emphasizes freeing the allocated memory. Final verification: The presenter combines the results in main and verifies the final sum.
Performance and Pitfalls
Why go through all this trouble? Performance. By dividing the work, we can take advantage of multiple CPU cores to finish the task faster. However, if not structured correctly, you can accidentally serialize your program and gain no benefit.
Watch this final clip to see a demonstration of the performance gain and a common pitfall.
Introduction To Threads (pthreads) | C Programming Tutorial
This final segment of the Portfolio Courses video demonstrates the "why" of multithreading and an important logical error to avoid.
First, watch the performance comparison between a single-threaded and multi-threaded summation. The speedup is significant. Then, watch the critical explanation of incorrect pthread_join placement. This shows how waiting for each thread immediately after creating it negates parallelism.
This last point is key: to achieve parallelism, you should launch all your worker threads first, and then join them all.
Conclusion
In this lesson, you have taken your first practical steps in concurrent programming. You have learned how to use the core POSIX Threads API to write a multi-threaded C program. We covered the essential functions (pthread_create, pthread_join, pthread_exit), the patterns for passing data to threads using structs, and two distinct methods for aggregating results: using a mutex-protected shared variable and returning values from threads. Finally, you saw the performance benefit of this approach and a critical pitfall to avoid.
The parallel summation example introduced a common problem in concurrent programming: the race condition. While we briefly used a mutex to solve it, our next lesson will formally define race conditions and explore how to identify them through code analysis. This will lay the groundwork for a deeper dive into the world of synchronization primitives in the following module.