三个参考链接:
url1: https://hpc-tutorials.llnl.gov/mpi/routine_args/
url2: https://hpc-tutorials.llnl.gov/mpi/blocking/
url3: https://hpc-tutorials.llnl.gov/mpi/non_blocking/
对于各个库函数/例程的介绍这里就不赘述,建议看三个参考 url 自行理解。
这里只研究样例代码。
样例1:ping pong (阻塞)
样例代码1 (精华都在注释):
进程0 给 进程1 发送 ping 并等待返回的 ping
#include "mpi.h" // MPI头文件
#include <stdio.h> // 标准输入输出头文件
int main(int argc, char *argv[]) {
// MPI相关变量声明
int numtasks, // MPI任务总数(进程数)
rank, // 当前任务的排名(进程ID)
dest, // 目标进程排名(消息发送目的地)
source, // 源进程排名(消息来源)
rc, // 返回值,用于检查MPI函数调用状态
count, // 实际接收到的消息元素数量
tag=1; // 消息标签,用于区分不同类型的消息
char inmsg, // 接收消息的缓冲区
outmsg='x'; // 发送消息,初始化为字符'x'
MPI_Status Stat; // MPI状态对象,用于接收操作中存储消息状态信息
// 初始化MPI环境
MPI_Init(&argc,&argv);
// 获取通信域中的进程总数
MPI_Comm_size(MPI_COMM_WORLD, &numtasks);
// 获取当前进程在通信域中的排名(ID)
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
// 任务0:先发送消息到任务1,然后等待接收任务1的回复
if (rank == 0) {
dest = 1; // 设置目标进程为任务1
source = 1; // 设置期望接收消息的源进程为任务1
// 阻塞发送:发送outmsg中的1个MPI_CHAR类型数据到目标进程
MPI_Send(&outmsg, 1, MPI_CHAR, dest, tag, MPI_COMM_WORLD);
// 阻塞接收:从源进程接收1个MPI_CHAR类型数据到inmsg
MPI_Recv(&inmsg, 1, MPI_CHAR, source, tag, MPI_COMM_WORLD, &Stat);
}
// 任务1:先等待接收任务0的消息,然后发送回复消息
else if (rank == 1) {
dest = 0; // 设置目标进程为任务0
source = 0; // 设置期望接收消息的源进程为任务0
// 阻塞接收:先接收任务0发来的消息
MPI_Recv(&inmsg, 1, MPI_CHAR, source, tag, MPI_COMM_WORLD, &Stat);
// 阻塞发送:然后发送回复消息给任务0
MPI_Send(&outmsg, 1, MPI_CHAR, dest, tag, MPI_COMM_WORLD);
}
// 查询接收状态并打印消息详情
// 获取实际接收到的数据元素数量
MPI_Get_count(&Stat, MPI_CHAR, &count);
// 打印接收信息:任务ID、接收字符数、来源任务、消息标签
printf("Task %d: Received %d char(s) from task %d with tag %d \n",
rank, count, Stat.MPI_SOURCE, Stat.MPI_TAG);
// 终止MPI环境
MPI_Finalize();
return 0; // 程序正常退出
}
编译运行方式:
mpicc test.c -o test
mpirun -np 2 ./test
预期输出:
Task 0: Received 1 char(s) from task 1 with tag 1
Task 1: Received 1 char(s) from task 0 with tag 1
样例2:环形拓扑 (非阻塞)
Nearest neighbor exchange in a ring topology

#include "mpi.h" // MPI头文件
#include <stdio.h> // 标准输入输出头文件
int main(int argc, char *argv[]) {
// MPI相关变量声明
int numtasks, // MPI任务总数(进程数)
rank, // 当前任务的排名(进程ID)
next, // 右邻居进程排名(环形拓扑)
prev, // 左邻居进程排名(环形拓扑)
buf[2], // 接收缓冲区,用于存储来自两个邻居的消息
tag1=1, // 消息标签1,用于区分消息类型
tag2=2; // 消息标签2,用于区分消息类型
MPI_Request reqs[4]; // 非阻塞操作请求句柄数组
MPI_Status stats[4]; // 状态数组,用于Waitall routine
// 初始化MPI环境
MPI_Init(&argc,&argv);
// 获取通信域中的进程总数
MPI_Comm_size(MPI_COMM_WORLD, &numtasks);
// 获取当前进程在通信域中的排名(ID)
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
// 确定左右邻居(创建环形进程拓扑)
prev = rank-1; // 左邻居通常是rank-1
next = rank+1; // 右邻居通常是rank+1
// 处理边界情况,形成环形拓扑
if (rank == 0) prev = numtasks - 1; // 第一个进程的左邻居是最后一个进程
if (rank == (numtasks - 1)) next = 0; // 最后一个进程的右邻居是第一个进程
// 发布非阻塞接收操作(立即返回,不等待消息到达)
// 从左邻居接收标签为tag1的消息,存储到buf[0]
MPI_Irecv(&buf[0], 1, MPI_INT, prev, tag1, MPI_COMM_WORLD, &reqs[0]);
// 从右邻居接收标签为tag2的消息,存储到buf[1]
MPI_Irecv(&buf[1], 1, MPI_INT, next, tag2, MPI_COMM_WORLD, &reqs[1]);
// 发布非阻塞发送操作(立即返回,不等待发送完成)
// 发送当前进程rank值给左邻居,使用标签tag2
MPI_Isend(&rank, 1, MPI_INT, prev, tag2, MPI_COMM_WORLD, &reqs[2]);
// 发送当前进程rank值给右邻居,使用标签tag1
MPI_Isend(&rank, 1, MPI_INT, next, tag1, MPI_COMM_WORLD, &reqs[3]);
// 在此处可以执行一些计算工作
// 非阻塞操作的优点:通信与计算可以重叠进行
// 当MPI库在后台处理消息传递时,程序可以继续执行其他计算任务
// do some work while sends/receives progress in background
// 等待所有非阻塞操作完成
// 阻塞在此处,直到4个请求(2个接收+2个发送)全部完成
MPI_Waitall(4, reqs, stats);
// 继续执行 - 进行更多工作
// 此时可以安全地使用buf中的数据,因为所有通信已完成
// continue - do more work
// 终止MPI环境
MPI_Finalize();
return 0; // 程序正常退出
}
编译运行命令:
mpicc test.c -o test
mpirun -np 4 ./test
预期没有输出

被折叠的 条评论
为什么被折叠?



