并行计算MPI(入门)——点对点通信
如上次所述,有两种类型的通信,一种是点对点通信(从现在起我们称之为 P2P),另一种是集体通信。 P2P 通信分为两种操作:发送(Send)和接收(Receive): 发送和接收。
最基本的 P2P 通信形式称为阻塞通信。 发送信息的进程会一直等待,直到接收进程接收完所有信息。 这是最简单的通信方式,但不一定是最快的,我们将在下面的课程中看到这一点。
一、发送信息
发送操作是向另一个进程发送某一类型的数据缓冲区。 P2P 消息具有以下属性
- 数据地址的引用
- 数据类型
- 数据数量
- 数据标签
- 目的地标识
- 通信器
让我们逐一介绍每一个元素,并详细说明它们是什么。
1.1数据地址的引用
引用始终是指向缓冲区的指针。 该数组将保存希望从当前进程发送到另一进程的数据。
1.2数据类型
数据类型必须与缓冲区中存储的数据精确对应。 为此,MPI 提供了可使用的预定义类型。 最常见的类型及其对应的 C 语言类型是 :
C Type | MPI Type |
---|---|
char | MPI_CHAR |
int | MPI_INT |
float | MPI_FLOAT |
double | MPI_DOUBLE |
还有很多其他类型,您可以在官方 MPI 标准文档中找到。 您也可以创建自己的数据类型。
1.3数据数量
这一项不言自明。 缓冲区中要发送到目的地的元素数量。
1.4数据标签
标签是一个简单的整数,用于识别通信的 “类型”。 这是一个完全非正式的值,由您自己设定。
1.5目的地标识
要发送数据的进程的rank。
1.6通信器
向哪个通讯器发送数据。 请记住,进程的级别可能会根据您选择的通讯器而改变。
二、接收信息
接收信息的方式与发送操作完全相同。 不过,调用时需要的不是目的地 id,而是源 id:即正在等待消息的进程的标识。 此外,根据您使用的是阻塞通信还是非阻塞通信,您还需要额外的参数,但我们会在适当的时间和地点介绍这些参数。 现在让我们来亲身体验一下。
三、实例
练习的目的如下: 程序将在两个进程中运行。 您的程序将从命令行中随机获得两个整数,并读入变量 local_value。 然后,根据进程的 id,你的程序会有不同的行为:
Process #0
- 将整数发送至 Process #1
- 接收Process #1的整数
- 将两个值的总和写入 sum
Process #1
- 接收Process #0 的整数
- 将整数发送至 Process #0
- 将两个值的乘积写入 product
注意这两个进程的前两个操作的顺序是颠倒的,这样我们就可以避免阻塞通信中最常见的问题之一:死锁。
3.1 发送信息代码
您可以使用 MPI_Send 命令向进程发送信息:
int MPI_Send(void *buf, int count , MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)
简单说明一下这几个参数:
- void *buf:提供发送信息的指针
- int count:发送信息的数目
- MPI_Datatype datatype:发送信息的数据类型
- int dest:发送信息的进程(rank)
- int tag:数据标签
- MPI_Comm comm:发送通信器
给几个简单的发送例子:
- 向进程 2 发送一个整数 :
int my_val = 10;
MPI_Send(&my_val, 1, MPI_INT, 2, 0, MPI_COMM_WORLD);
- 向进程 1 发送整数表:
int values[5] = {1, 2, 3, 4, 5};
MPI_Send(values, 5, MPI_INT, 1, 0, MPI_COMM_WORLD);
- 向处理 10 发送带有特定标记 6 的双精度表:
double values[3] = {1e25, -0.0, M_PI};
MPI_send(values, 3, MPI_DOUBLE, 10, 6, MPI_COMM_WORLD);
3.2 接收信息代码
从进程接收数据所调用的函数与发送数据的函数非常相似,但多了一个参数:
int MPI_Recv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status);
正如你所看到的,还有一个额外的状态参数。 我们暂且忽略它,将其值设为MPI_STATUS_IGNORE,在下一个练习中我们会看到更多关于它的信息。
除了常规参数外,还有两个通配符可以使用。 MPI_ANY_SOURCE(MPI_ANY_SOURCE)而不是一个精确的报文来源 ID,表示可以接收来自任何来源的报文。 MPI_ANY_TAG 意义相同,但适用于任何标记。
最后,"计数 "参数表示在通信中预计收到的元素的最大数量。 因此,实际收到的元素数量可以少于或等于此数。 如果收到的元素较多,则会触发错误。
现在我们来看几个调用 MPI_Recv 的示例:
- 从进程 5 接收一个标记为 0 的整数:
int value;
MPI_Recv(&value, 1, MPI_INT, 5, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
- 从流程 3 收到一个包含 10 个整数的表,没有标记:
int values[10];
MPI_Recv(&values, 10, MPI_INT, 3, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
- 接收来自任何程序的 3 个加倍表,并带有任何标记 :
double values[3];
MPI_Recv(&values, 3, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
3.3 完整代码
#include <iostream>
#include <mpi.h>
#include <cstdlib>
using namespace std;
int main(int argc, char** argv) {
MPI_Init(&argc, &argv);
int rank;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
// Read the local value of the process
// local_value will hold a specific int for process 0, and another for process 1
int local_value=1;
//local_value = atoi(argv[1]);//atoi将字符串数字转化为整数
int other_value;
if (rank == 0) {
// Here, enter the code for the first process :
// 1- Send the value to process 1
// 2- Receive the value from process 1 (in other_value)
// 3- Print the sum of the two values on stdout
MPI_Send(&local_value, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);
MPI_Recv(&other_value, 1, MPI_INT, 1, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
int sum = local_value + other_value;
cout << local_value<< "," << other_value<<"," << sum << endl;
}
else {
// Here enter the code for the second process :
// 1- Receive the value from process 0 (in other_value)
// 2- Send the value to process 0
// 3- Print the product of the two values on stdout
MPI_Recv(&local_value, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
other_value = 2;
MPI_Send(&other_value, 1, MPI_INT, 0, 1, MPI_COMM_WORLD);
int product = local_value * other_value;
cout << local_value <<"," << other_value <<"," << product << endl;
}
MPI_Finalize();
return 0;
}