Introduction
In our last lesson, you successfully wrote, compiled, and ran your first MPI program. You learned how to initialize the MPI environment and use MPI_Comm_rank and MPI_Comm_size to implement the Single Program, Multiple Data (SPMD) model. However, the processes in that program ran in isolation; they didn't communicate.
This lesson takes the crucial next step: enabling processes to exchange data. This is the heart of the message-passing paradigm. We will focus on two fundamental functions, MPI_Send and MPI_Recv, which facilitate point-to-point communication. By the end of this lesson, you will be able to implement communication between specific processes to solve a coordinated task.
Let's start with a simple visualization of what we're about to build.

1. The Anatomy of MPI_Send and MPI_Recv
Point-to-point communication involves a pair of functions: one to send data and one to receive it. In MPI, these are MPI_Send and MPI_Recv. Their function prototypes have several arguments that give you precise control over the data exchange.
Let's dive into a detailed explanation of these functions and their parameters.
MPI Send and Receive - MPI Tutorial
The MPI Tutorial website offers a clear breakdown of the MPI_Send and MPI_Recv functions. Please read the following sections to understand their structure and the meaning of each argument.
Please read the sections titled 'Overview of sending and receiving with MPI', 'Prototypes for MPI_Send and MPI_Recv', and 'Elementary MPI datatypes'. As you read, focus on: The conceptual 'envelope' model for packing data. The purpose of each argument in the MPI_Send and MPI_Recv functions. The role of MPI Datatypes for ensuring portability.
Let's review the C++ function prototypes and their arguments, as you will be using them directly.
MPI_Send
int MPI_Send(
void* data,
int count,
MPI_Datatype datatype,
int destination,
int tag,
MPI_Comm communicator
);
void* data: A pointer to the memory buffer containing the data you want to send. Since you're experienced with C++, you'll recognize the use ofvoid*to create a generic interface that can handle any data type.int count: The number of elements in the buffer, not the number of bytes.MPI_Datatype datatype: An MPI constant specifying the type of each element (e.g.,MPI_INT,MPI_DOUBLE). MPI uses this to correctly interpret the data on potentially different machine architectures.int destination: The rank of the process that will receive the message.int tag: An integer you can use to label or differentiate messages. The receiver can request a message with a specific tag.MPI_Comm communicator: The group of processes this communication belongs to, which will almost always beMPI_COMM_WORLDfor now.
MPI_Recv
int MPI_Recv(
void* data,
int count,
MPI_Datatype datatype,
int source,
int tag,
MPI_Comm communicator,
MPI_Status* status
);
The first six arguments mirror MPI_Send, but with a receiver's perspective:
data,count,datatype: Describe the buffer where incoming data will be stored.counthere specifies the maximum number of elements the buffer can hold.source: The rank of the process you expect the message from. You can useMPI_ANY_SOURCEif you don't know or care.tag: The tag of the message you want to receive.MPI_ANY_TAGis also valid here.MPI_Status* status: A pointer to a status object. After the receive completes, MPI fills this object with information about the message that was actually received, such as its true source, tag, and size. If you don't need this information, you can simply passMPI_STATUS_IGNORE.
2. A Master-Worker Example
Now, let's see a practical example. A common pattern in parallel computing is "master-worker," where one process (the master, usually rank 0) distributes tasks to other processes (the workers) and collects their results.
The following video demonstrates exactly this pattern. Rank 0 sends a unique integer to each worker. Each worker squares the integer it receives and sends the result back to rank 0.
The video 'Parallel C++: MPI' by Nick provides an excellent walkthrough of a C++ master-worker program using MPI_Send and MPI_Recv.
Please watch from 08:40 to 16:14. Pay close attention to: Conditional Logic (09:20): How the program uses an if (taskID == 0) block to separate the code for the master process from the code for the worker processes. Sending Loop (10:18): The master's first loop, where it calls MPI_Send to distribute data to each worker. Receiving in Workers (13:30): How each worker calls MPI_Recv to get its assigned task from rank 0. Sending from Workers (14:30): How workers send their results back to the master. Receiving in Master (11:58): The master's second loop, where it calls MPI_Recv to collect the results.
This example elegantly shows how MPI_Send and MPI_Recv work in pairs to coordinate a task. The master sends to worker i, and worker i receives from the master. Then, worker i sends back to the master, and the master receives from worker i.
3. Blocking Communication and the Danger of Deadlock
The functions MPI_Send and MPI_Recv are blocking. This is a critical concept with profound implications for how you structure your code.
- A blocking
MPI_Recvwill "block" (i.e., pause the execution of that process) until the message it is waiting for has fully arrived. - A blocking
MPI_Sendwill block until the message has been safely copied out of your application's memory buffer, either into a system buffer or across the network. OnceMPI_Sendreturns, you are free to reuse the send buffer.
This blocking behavior ensures that communications are completed correctly, but it also creates a classic trap in parallel programming: deadlock.
Imagine two processes, 0 and 1, that need to exchange data. Consider this flawed logic:
- Process 0: Calls
MPI_Sendto send data to process 1. Then callsMPI_Recvto get data from process 1. - Process 1: Calls
MPI_Sendto send data to process 0. Then callsMPI_Recvto get data from process 0.
Both processes call MPI_Send and block, waiting for the other process to post a matching receive. But neither process can get to its MPI_Recv call because it's stuck in MPI_Send. They will wait forever. This is a deadlock.

The solution is to ensure that for every Send, a matching Recv can be executed. Let's look at a resource that explains this problem and its solution clearly.
MPI_Send and MPI_Recv - GitHub Pages
This guide from PDC Support explains the concept of blocking and provides a clear example of a deadlock situation and how to fix it.
Please read the section titled 'Blocking'. Pay careful attention to the first code example, which is designed to deadlock. Then, study the 'Solution' code that follows. Note how it reorders the operations for different ranks to avoid the deadlock.
As the resource demonstrates, the deadlock is resolved by breaking the symmetry. Instead of all processes doing Send then Recv, you can structure it like this:
- Process 0:
MPI_Send(to=1), thenMPI_Recv(from=1) - Process 1:
MPI_Recv(from=0), thenMPI_Send(to=0)
Now, Process 0's send can complete because Process 1 immediately posts a matching receive. Once that transfer is done, Process 1's send can complete because Process 0 has moved on to its receive call. A common way to implement this for many processes is to have even-ranked processes send then receive, while odd-ranked processes receive then send.
4. Hands-on Practice: The Ring
Now it's time to write some code. A classic MPI exercise is to pass a message around a "ring" of processes. Process 0 sends a value to process 1, process 1 sends it to process 2, and so on, until the last process sends it back to 0.
Your Task:
Write a C++ MPI program that implements this ring communication.
- Process 0 should initialize an integer
tokento some value (e.g., 42). - Process 0 sends this
tokento process 1. - Every other process
ishould:
a. Receive thetokenfrom processi-1.
b. Print a message like:Process <i> received token <value> from process <i-1>.
c. Send thetokento processi+1. - The communication should wrap around: the last process (
world_size - 1) sends the token back to process 0. - Process 0, after its initial send, must wait to receive the token from the last process and print a final confirmation message.
Hint: Be very careful about deadlock! A naive implementation where every process (including 0) tries to receive before sending will deadlock, as process 0 has nothing to receive initially. You need special logic for process 0. Think about the correct sequence of Send and Recv calls for rank 0 versus all other ranks.
Take about 15 minutes to try and implement this. A correct implementation is shown in the MPI Tutorial resource under the "Ring Program" section ([resource ID LINK](https://mpitutorial.com/tutorials/mpi-send-and-receive/)). Refer to it if you get stuck, but try to solve the logic yourself first.
Conclusion
In this lesson, you have mastered the fundamentals of point-to-point communication in MPI. This is a huge leap from our previous lesson and unlocks the ability to write truly collaborative parallel programs.
Key Takeaways:
- Point-to-Point Functions:
MPI_SendandMPI_Recvare the core functions for exchanging data between two specific processes. - Function Parameters: You now understand how to use the arguments
data,count,datatype,destination/source,tag, andcommunicatorto control a data transfer. - Blocking Behavior: Both
MPI_SendandMPI_Recvare blocking calls, meaning they pause execution until the communication is safe to complete. - Deadlock Avoidance: You have learned to identify and prevent deadlock by carefully structuring the order of send and receive operations, ensuring that for every
MPI_Send, a correspondingMPI_Recvis posted by the partner process.
Preview of the Next Lesson
While blocking communication is safe and easy to reason about, it can be inefficient. A process might sit idle waiting for a communication to complete when it could be doing other useful work. In the next lesson, we will explore non-blocking communication using MPI_Isend, MPI_Irecv, and MPI_Wait. This advanced technique allows you to overlap communication with computation, a key strategy for maximizing performance in scalable systems.