【MPI】阻塞点对点通信
Blocking Point-to-Point Communication in MPI
本文将介绍 MPI 中的阻塞式点对点通信,包括基本数据类型、MPI_Send
和 MPI_Recv
函数、MPI_Status
结构体,以及 MPI_Get_count
和 MPI_Probe
函数。
MPI 基础数据类型
在 MPI 中,数据类型(datatype)是数据结构的重要部分,用于定义消息传递中数据的格式。MPI 提供了一些基础数据类型,简洁明了对应于 C 语言中的基本数据类型:
MPI datatype | C equivalent |
---|---|
MPI_SHORT | short int |
MPI_INT | int |
MPI_LONG | long int |
MPI_LONG_LONG | long long int |
MPI_UNSIGNED_CHAR | unsigned char |
MPI_UNSIGNED_SHORT | unsigned short int |
MPI_UNSIGNED | unsigned int |
MPI_UNSIGNED_LONG | unsigned long int |
MPI_UNSIGNED_LONG_LONG | unsigned long long int |
MPI_FLOAT | float |
MPI_DOUBLE | double |
MPI_LONG_DOUBLE | long double |
MPI_BYTE | char |
此外,MPI 还允许构造复杂的数据类型,以便传递自定义的复合结构。
MPI_Send 和 MPI_Recv
在 MPI 中,阻塞式的点对点通信函数主要是 MPI_Send
和 MPI_Recv
。它们分别用于在进程之间发送和接收消息。所谓“阻塞”是指函数会在数据发送或接收完成后才返回,确保通信的完整性。
MPI_Send
MPI_Send
是发送消息的阻塞式函数,将数据从一个进程发送到另一个进程。MPI_Send
的发送操作会在数据被成功复制到系统缓存或目标进程接收到数据之前阻塞发送进程,从而确保数据一致性。 其函数原型如下:
int MPI_Send(const void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm);
buf
:指向待发送数据的指针。count
:待发送的数据个数。datatype
:发送数据的数据类型(如MPI_INT
)。dest
:目标进程的 rank(编号)。tag
:消息标签,用于区分不同类型的消息。comm
:通信域(如MPI_COMM_WORLD
)。
例如,进程 0 可以使用以下代码将一个整型数组发送给进程 1:
int numbers[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
MPI_Send(numbers, 10, MPI_INT, 1, 0, MPI_COMM_WORLD);
MPI_Recv
MPI_Recv
是接收消息的阻塞式函数,它在消息被完全接收到本地缓冲区后才返回。其函数原型如下:
int MPI_Recv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status);
buf
:指向接收缓冲区的指针。count
:预期接收的数据个数。datatype
:接收数据的数据类型(如MPI_INT
)。source
:消息源进程的 rank。如果设置为MPI_ANY_SOURCE
,则可以接收来自任何进程的消息。tag
:消息标签。如果设置为MPI_ANY_TAG
,则可以接收任何标签的消息。comm
:通信域(如MPI_COMM_WORLD
)。status
:指向MPI_Status
结构体的指针,用于存储消息的相关信息。
例如,进程 1 可以使用以下代码接收进程 0 发送的整型数组:
int numbers[10];
MPI_Status status;
MPI_Recv(numbers, 10, MPI_INT, 0, 0, MPI_COMM_WORLD, &status);
MPI_Status 结构体
MPI_Status
结构体用于存储消息传递完成后的相关信息,例如消息的来源、标签和接收到的数据量。MPI_Recv
会通过 status
参数返回消息的信息。MPI_Status
结构体的常用字段如下:
MPI_SOURCE
:消息的来源进程的 rank。MPI_TAG
:消息的标签。MPI_ERROR
:错误代码。
例如,可以通过 status
获取消息的来源和标签:
printf("Message source = %d, tag = %d\n", status.MPI_SOURCE, status.MPI_TAG);
MPI_Get_count
MPI_Get_count
函数用于获取接收消息的实际数据量。由于 MPI_Recv
函数需要提供一个缓冲区和数据量的上限(count
),但实际接收到的数据量可能少于 count
,因此可以使用 MPI_Get_count
来获取接收的实际数据量。
其函数原型如下:
int MPI_Get_count(const MPI_Status *status, MPI_Datatype datatype, int *count);
status
:MPI_Status
结构体,通常是MPI_Recv
返回的status
。datatype
:数据类型。count
:指向整数的指针,用于存储实际接收的数据量。
例如:
int actual_count;
MPI_Get_count(&status, MPI_INT, &actual_count);
printf("Received %d integers\n", actual_count);
MPI_Probe
MPI_Probe
是一种非阻塞的消息探测函数,允许进程在不接收消息的情况下检查是否有特定消息到达。这在动态内存分配等场景中非常有用,因为我们可以在接收数据前确定消息的长度。
其函数原型如下:
int MPI_Probe(int source, int tag, MPI_Comm comm, MPI_Status *status);
source
:消息来源进程的 rank。如果设置为MPI_ANY_SOURCE
,则可以探测来自任何进程的消息。tag
:消息标签。如果设置为MPI_ANY_TAG
,则可以探测任何标签的消息。comm
:通信域。status
:指向MPI_Status
结构体的指针,用于存储消息的相关信息。
例如,以下代码段使用 MPI_Probe
来检查消息长度:
MPI_Status status;
MPI_Probe(0, 0, MPI_COMM_WORLD, &status);
int count;
MPI_Get_count(&status, MPI_INT, &count);
int *buffer = (int *)malloc(count * sizeof(int));
MPI_Recv(buffer, count, MPI_INT, 0, 0, MPI_COMM_WORLD, &status);
结论
MPI 的阻塞式点对点通信在并行编程中具有重要意义。通过使用 MPI_Send
和 MPI_Recv
函数,进程可以在不同的节点之间传递数据,确保消息的可靠传输。同时,利用 MPI_Status
、MPI_Get_count
和 MPI_Probe
等辅助函数,可以更灵活地控制消息传递过程。
实例
#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
//int main() {
// const int PING_PONG_LIMIT = 10;
// MPI_Init(NULL, NULL);
// int rank, size;
// MPI_Comm_rank(MPI_COMM_WORLD, &rank);
// MPI_Comm_size(MPI_COMM_WORLD, &size);
//
//
// int ping_pong_count = 0;
// int partner_rank = (rank + 1) % 2;
// while (ping_pong_count < PING_PONG_LIMIT) {
// if (rank == ping_pong_count % 2) {
// ping_pong_count++;
// // 注意这个ping_pong_count被作为缓存,所以看起来两个进程共享ping_pong_count变量,但实际上两个进程是独立的
// MPI_Send(&ping_pong_count, 1, MPI_INT, partner_rank, 0, MPI_COMM_WORLD);
// printf("rank %d: sent and incremented count %d to rank %d\n", rank, ping_pong_count, partner_rank);
// } else {
// MPI_Recv(&ping_pong_count, 1, MPI_INT, partner_rank, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
// printf("rank %d: received count %d from rank %d\n", rank, ping_pong_count, partner_rank);
// }
// }
//
// MPI_Finalize();
//}
//int main() {
// MPI_Init(NULL, NULL);
// int rank, size;
// MPI_Comm_rank(MPI_COMM_WORLD, &rank);
// MPI_Comm_size(MPI_COMM_WORLD, &size);
//
// int token;
// if (rank) {
// MPI_Recv(&token, 1, MPI_INT, rank - 1, 0, MPI_COMM_WORLD,
// MPI_STATUS_IGNORE);
// printf("Process %d received token %d from process %d\n", rank, token,
// rank - 1);
// } else {
// token = -1;
// }
//
// MPI_Send(&token, 1, MPI_INT, (rank + 1) % size, 0, MPI_COMM_WORLD);
// if (rank == 0) {
// MPI_Recv(&token, 1, MPI_INT, size - 1, 0, MPI_COMM_WORLD,
// MPI_STATUS_IGNORE);
// printf("Process %d received token %d from process %d\n", rank, token,
// size - 1);
// }
//
// MPI_Finalize();
//}
#include <time.h>
//int main() {
// MPI_Init(NULL, NULL);
// int rank, size;
// MPI_Comm_rank(MPI_COMM_WORLD, &rank);
// MPI_Comm_size(MPI_COMM_WORLD, &size);
//
// if (size != 2) {
// fprintf(stderr, "Must use two processes for this exapmle\n");
// // 在并行计算进程中终止程序,1是erroecode
// MPI_Abort(MPI_COMM_WORLD, 1);
// }
//
// const int MAX_NUMBER = 100;
// int numbers[MAX_NUMBER];
// int num_cnt;
// if (rank==0) {
// srand(time(NULL));
// // rand()/(float)RAND_MAX生成0~1之间的随机数
// num_cnt=(rand()/(float)RAND_MAX)*MAX_NUMBER;
// MPI_Send(numbers,num_cnt, MPI_INT, 1, 666, MPI_COMM_WORLD);
// printf("rank %d: sent %d numbers to rank %d\n", rank, num_cnt, 1);
// } else if (rank == 1) {
// MPI_Status status;
// MPI_Recv(numbers, MAX_NUMBER, MPI_INT, 0, MPI_ANY_TAG, MPI_COMM_WORLD,&status);
// MPI_Get_count(&status, MPI_INT, &num_cnt);
// printf("1 received %d numbers from 0. Message source = %d,tag = %d\n",
// num_cnt, status.MPI_SOURCE, status.MPI_TAG);
// }
//
// MPI_Finalize();
// return 0;
//}
int main() {
MPI_Init(NULL, NULL);
int rank, size;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
if (size != 2) {
fprintf(stderr, "Must use two processes for this exapmle\n");
// 在并行计算进程中终止程序,1是erroecode
MPI_Abort(MPI_COMM_WORLD, 1);
}
int num_cnt;
if (rank==0) {
const int MAX_NUMBER = 100;
int numbers[MAX_NUMBER];
srand(time(NULL));
// rand()/(float)RAND_MAX生成0~1之间的随机数
num_cnt=(rand()/(float)RAND_MAX)*MAX_NUMBER;
MPI_Send(numbers,num_cnt, MPI_INT, 1, 666, MPI_COMM_WORLD);
printf("rank %d: sent %d numbers to rank %d\n", rank, num_cnt, 1);
} else if (rank == 1) {
MPI_Status status;
MPI_Probe(0, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
MPI_Get_count(&status, MPI_INT, &num_cnt);
int* numbers = (int*)malloc(sizeof(int) * num_cnt);
MPI_Recv(numbers, num_cnt, MPI_INT, 0, MPI_ANY_TAG, MPI_COMM_WORLD,
&status);
printf("1 dynamically received %d numbers from 0. Message source = %d,tag = %d\n",
num_cnt, status.MPI_SOURCE, status.MPI_TAG);
}
MPI_Finalize();
return 0;
}
参考:传送门