Introduction
In our previous lesson, we established the theoretical foundation for distributed-memory computing. We explored the message-passing paradigm, where independent processes communicate by explicitly sending and receiving data, and the SPMD (Single Program, Multiple Data) model, where every process runs the same program but performs different work based on its unique rank.
Today, we transition from theory to practice. This lesson is your hands-on introduction to the Message Passing Interface (MPI). You will learn how to write, compile, and run a basic MPI program using C++. We will focus on the four most fundamental MPI functions that form the backbone of nearly every MPI application:
MPI_InitMPI_FinalizeMPI_Comm_rankMPI_Comm_size
By the end of this lesson, you will have a complete, working parallel program and a clear understanding of the development workflow for MPI applications.
1. The Core MPI Functions and Program Structure
Every MPI program follows a standard structure. It begins by initializing the MPI environment, then performs its parallel work, and finally, shuts down the environment. This entire parallel section of your code is enclosed by MPI_Init and MPI_Finalize.
Within this parallel region, two essential pieces of information are needed for the SPMD model to work:
- How many total processes are part of this job?
- Who am I within that group of processes?
Let's watch a video that walks through the C++ code for a basic MPI application and explains the functions that provide this structure and information.
The video 'Parallel C++: MPI' from the Nick channel provides a clear, practical demonstration of the fundamental MPI functions in a C++ context.
Please watch from 02:23 to 06:34. As you watch, focus on: The MPI Environment (02:23 - 03:42): Note the inclusion of mpi.h and how MPI_Init and MPI_Finalize create a block for all parallel code. Communicators and Ranks (03:42 - 04:39): Understand the concept of a 'communicator' (MPI_COMM_WORLD) as the group of all processes, and a 'rank' as the unique ID of a single process within that group. Getting Rank and Size (05:11 - 06:34): See the C++ code that uses MPI_Comm_size to get the total number of processes and MPI_Comm_rank to get the current process's ID. Notice how pointers are passed to these functions so they can write the results back into your variables.
To summarize the structure discussed in the video, here is a flowchart that visualizes the essential components of an MPI program.

2. Your First MPI Program: "Hello, World!"
Now, let's put these pieces together into a complete C++ program. The following code is a classic "Hello, World!" example adapted for MPI. It perfectly demonstrates the SPMD model: every process runs this exact same code, but because MPI_Comm_rank gives each one a different my_rank, the output from each process is unique.
The 'MPI Hands-On - C++' guide from MolSSI Education provides an excellent, clean C++ example. We will use their code as our starting point.
Please read the section 'Getting Started with MPI'. Focus on the C++ code block under this heading. Compare it to the concepts from the video and identify the four key functions we've discussed. Don't worry about the compilation and execution commands just yet; we'll cover those next.
Here is the C++ code from the resource for your reference.
#include <iostream>
#include <mpi.h>
int main(int argc, char **argv) {
// Initialize the MPI environment.
// This must always be called before any other MPI functions.
MPI_Init(&argc, &argv);
// Get the number of processes in MPI_COMM_WORLD.
int world_size;
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
// Get the rank of this process in MPI_COMM_WORLD.
int my_rank;
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
// Print out a message.
std::cout << "Hello from rank " << my_rank
<< " out of " << world_size << " processes." << std::endl;
// Finalize the MPI environment.
// This must always be called after all other MPI functions.
MPI_Finalize();
return 0;
}
Notice MPI_Init takes the address of argc and argv. This allows the MPI system to parse command-line arguments that are meant for it (e.g., network configuration details) and remove them, leaving only the arguments intended for your application.
3. Compiling and Running your MPI Program
You can't compile and run an MPI program like a standard C++ application. You need special tools that know how to link the MPI libraries and launch processes across a system.
Compiling with mpic++
MPI implementations provide a compiler wrapper, typically named mpic++ for C++ code. This wrapper calls a standard C++ compiler (like g++ or clang++) but automatically adds the necessary flags to include MPI headers and link against the MPI libraries. For you, the process is simple: just replace your usual g++ command with mpic++.
Running with mpirun or mpiexec
To execute your compiled program, you use a launcher like mpirun or mpiexec. Its most important job is to start multiple copies of your program. The -n flag specifies the number of processes to launch.
Let's return to the video, which demonstrates this workflow.
The next segment of the 'Parallel C++: MPI' video shows the exact commands for compiling with mpic++ and running with mpirun.
Watch from 06:46 to 08:40. Pay close attention to: The mpic++ command used for compilation. The mpirun -n <number> command for execution. The output of the program. Notice how the order in which the 'Hello' messages appear is not guaranteed. This is a critical feature of parallel execution—there is no default synchronization between the processes' print statements.
To summarize the commands, if your file is named hello.cpp, the workflow is:
- Compile:
mpic++ hello.cpp -o hello_mpi - Run with 4 processes:
mpirun -n 4 ./hello_mpi
You will likely see an output like this, though the order may vary:
Hello from rank 2 out of 4 processes.
Hello from rank 0 out of 4 processes.
Hello from rank 3 out of 4 processes.
Hello from rank 1 out of 4 processes.
If you have an MPI implementation installed on your system, I encourage you to save the code above, compile it, and run it yourself. Try changing the number of processes with the -n flag.
4. A Note on Error Handling
In a distributed application, if one process encounters a fatal error, you typically want the entire job to terminate gracefully, rather than leaving "zombie" processes running. Simply calling exit() or return 1 from one process is not sufficient, as it only terminates that single process.
The correct way to abort an entire MPI job is with MPI_Abort.
MPI_Abort(MPI_COMM_WORLD, error_code);
This function terminates all processes in the MPI_COMM_WORLD communicator. It's good practice to use this for handling unrecoverable errors in your MPI applications.
Conclusion
Congratulations! You have just learned how to write, compile, and run a parallel program using MPI. This is a significant first step into the world of distributed-memory programming.
Key Takeaways:
- Program Structure: All MPI C++ programs must include
<mpi.h>, and all parallel code must be contained betweenMPI_Init()andMPI_Finalize(). - Core Functions:
MPI_Comm_size()reports the total number of processes, andMPI_Comm_rank()provides the unique ID for the current process. These are the building blocks of the SPMD model. - Workflow: MPI code is compiled with
mpic++(or a similar wrapper) and executed withmpirunormpiexec, using the-nflag to specify the process count. - Execution is Asynchronous: Processes run independently. Without explicit synchronization, the order of operations (like printing to the screen) between processes is not guaranteed.
Preview of the Next Lesson
Our "Hello, World!" program demonstrated the SPMD model, but the processes didn't actually communicate with each other. They each ran in isolation. In the next lesson, we will change that. You will learn how to implement point-to-point communication using blocking MPI_Send and MPI_Recv. This will allow your processes to explicitly exchange data, which is the heart of the message-passing paradigm.