丁致宇第三周学习报告:MPI学习
[TOC]
MPI基础
一个基本的MPI程序框架
#include <mpi.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
// 初始化MPI环境
MPI_Init(&argc, &argv);
// 获取当前进程的排名
int world_rank;
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
// 获取总进程数
int world_size;
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
// 让每个进程打印出它的排名和总的进程数
printf("Hello world from rank %d out of %d processors\n", world_rank, world_size);
// 清理MPI环境
MPI_Finalize();
return 0;
}
计时框架
#include <stdio.h>
#include <mpi.h>
int main(int argc, char *argv[])
{
MPI_Init(&argc, &argv);
int rank;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
int size;
MPI_Comm_size(MPI_COMM_WORLD, &size);
double start_time = MPI_Wtime();
// ... 在这里执行你的并行代码 ...
// ... 在这里执行你的并行代码 ...
double end_time = MPI_Wtime();
double elapsed_time = end_time - start_time;
// 在所有进程中找到最大的运行时间
double max_elapsed_time;
MPI_Reduce(&elapsed_time, &max_elapsed_time, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);
// 在主进程中打印最大运行时间
if (rank == 0)
{
printf("\ntime:\n");
printf("Max elapsed time: %f seconds\n", max_elapsed_time);
}
MPI_Finalize();
return 0;
}
编译运行
要编译和运行这个MPI程序,你需要安装MPI库并使用支持MPI的编译器,比如mpicc
。编译命令可能如下所示:
mpicc -o mpi_hello_world mpi_hello_world.c
运行MPI程序时,你需要使用mpirun
或mpiexec
命令,并指定进程数,例如:
mpirun -np 4 ./mpi_hello_world
这条命令会启动4个进程运行你的程序。每个进程都会打印出它的排名和总进程数,但是打 印的顺序可能是不确定的,因为它们是并行运行的。
点对点通信
MPI(Message Passing Interface)是一个通信协议,用于编程在各个不同节点上运行的并行计算机之间的进程通信。它被设计用来在分布式内存系统上工作,这样的系统通常没有全局地址空间。在这样的系统中,进程间的通信需要通过发送和接收消息来实现。
MPI_Send 函数
MPI_Send
是 MPI 中用于发送消息的基本函数。它的原型如下:
int MPI_Send(const void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)
参数解释:
buf
:指向待发送数据的缓冲区的指针。count
:缓冲区中数据元素的数量。datatype
:发送数据元素的数据类型。dest
:目标进程的排名(rank)。tag
:消息的标签,接收方可以根据这个标签来选择性地接收消息。comm
:通信器(communicator),定义了一个进程组和它们之间的通信上下文。
MPI_Recv 函数
MPI_Recv
是 MPI 中用于接收消息的基础函数。它的原型如下:
int MPI_Recv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)
参数解释:
buf
:指向接收数据的缓冲区的指针。count
:缓冲区中数据元素的最大数量。datatype
:接收数据元素的数据类型。source
:源进程的排名(rank)。tag
:消息的标签,与发送时的标签相对应。comm
:通信器。status
:一个结构体,用于返回关于接收到的消息的信息,如源排名、标签和错误码。
示例
下面是一个简单的 MPI 程序示例,其中使用了 MPI_Send
和 MPI_Recv
函数来在两个进程之间传递一个整数消息。
#include <mpi.h>
#include <stdio.h>
int main(int argc, char** argv) {
// 初始化 MPI 环境
MPI_Init(&argc, &argv);
// 获取总的进程数
int world_size;
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
// 获取当前进程的排名
int world_rank;
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
int number;
if (world_rank == 0) {
// 如果是排名为 0 的进程,则发送一个整数到排名为 1 的进程
number = -1;
MPI_Send(&number, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);
} else if (world_rank == 1) {
// 如果是排名为 1 的进程,则从排名为 0 的进程接收一个整数
MPI_Recv(&number, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
printf("Process 1 received number %d from process 0\n", number);
}
// 清理 MPI 环境
MPI_Finalize();
return 0;
}
在这个例子中,进程 0 发送一个整数(-1)给进程 1。进程 1 接收这个整数并打印出来。请注意,这段代码需要在支持 MPI 的环境中编译和运行,通常使用如下命令:
mpicc -o mpi_example mpi_example.c
mpirun -np 2 ./mpi_example
这里,mpicc
是用于编译 MPI 程序的编译器,而 mpirun
用于启动 MPI 程序,-np 2
指定了使用两个进程。
注意
Send、Recv函数中的buf参数可以不同吗?
在MPI中,MPI_Send
和MPI_Recv
函数用于在进程间发送和接收消息。这些函数中的buf
参数指的是发送和接收数据的缓冲区。在发送和接收操作中,缓冲区的数据类型和数量应该匹配,但实际的缓冲区指针(即内存地址)是可以不同的。
这里是一个简单的说明:
- 发送方 (
MPI_Send
):buf
参数是指向发送缓冲区的指针,这个缓冲区包含了要发送的数据。 - 接收方 (
MPI_Recv
):buf
参数是指向接收缓冲区的指针,这个缓冲区用于存储接收到的数据。
发送方和接收方的buf
参数通常指向它们各自进程中的不同内存位置。这是因为在不同的进程中,内存空间是隔离的,所以即便是相同的相对地址,在不同的进程中也可能映射到不同的物理内存。
确保通信正确进行的关键在于buf
参数所指向的缓冲区大小和数据类型应该与在MPI_Send
和MPI_Recv
调用中指定的count
和datatype
参数相匹配 。如果发送和接收操作的数据类型或数量不匹配,可能会导致数据错误或程序崩溃。
这里有一个简单的例子:
发送方:
int send_data[10]; // 发送缓冲区
MPI_Send(send_data, 10, MPI_INT, dest, tag, MPI_COMM_WORLD);
接收方:
int recv_data[10]; // 接收缓冲区
MPI_Recv(recv_data, 10, MPI_INT, source, tag, MPI_COMM_WORLD, &status);
在这个例子中,发送方有一个名为send_data
的数组,接收方有一个名为recv_data
的数组。虽然它们是不同的数组(可能位于不同的内存位置),但它们都是大小为10的整型数组,因此可以正确匹配。
MPI预定义的数据类型(MPI_Datatype datatype)

MPI中的通配符
MPI(Message Passing Interface)是一个标准化且可移植的消息传递系统,设计用于由各种计算机组成的并行计算机。MPI标准定义了一系列的API用于进程间通信,这些API包括了一系列的通配符(wildcards)和特殊常量,它们用于简化编程,并提供灵活的通信模式。
以下是MPI中几个常见的通配符和特殊常量:
-
MPI_ANY_SOURCE
:- 这个通配符用于
MPI_Recv
和相关函数中的源地址参数,表示接收来自任何进程的消息。 - 使用
MPI_ANY_SOURCE
时,如果有多个消息都满足接收条件,MPI将根据其内部机制选择一个消息接收。 - 由于不指定具体的发送源,因此通常需要通过
MPI_Status
对象来确定实际消息的发送源。
- 这个通配符用于
-
MPI_STATUS_IGNORE
:- 这是一个特殊的参数,可以在需要
MPI_Status
对象的函数调用中使用,表示用户对状态信息不感兴趣,可以忽略。 - 使用
MPI_STATUS_IGNORE
可以减少一些系统开销,因为MPI不需要去填充状态对象。 - 如果使用了
MPI_STATUS_IGNORE
,就无法获取关于接收消息的详细信息,如发送者的标识、消息的标签等。
- 这是一个特殊的参数,可以在需要
-
MPI_ANY_TAG
:- 类似于
MPI_ANY_SOURCE
,这个通配符用于MPI_Recv
和相关函数中的消息标签参数,表示接收任何标签的消息。 - 使用
MPI_ANY_TAG
时,接收到的消息可以是任何匹配其他条件(如发送源)的标签。
- 类似于
-
MPI_PROC_NULL
:- 这个常量用于表示一个“空”进程,可以用在 发送和接收操作中,表示不进行实际的消息传递。
- 当使用
MPI_PROC_NULL
作为目标时,发送操作会变成一个空操作,不会有任何消息被发送。 - 当使用
MPI_PROC_NULL
作为源时,接收操作会立即返回,状态对象会显示消息来源为MPI_PROC_NULL
。
使用这些通配符和特殊常量时的注意事项:
- 当使用
MPI_ANY_SOURCE
或MPI_ANY_TAG
时,你通常需要检查状态对象来确定消息的实际来源和标签。 - 如果你打算忽略状态信息,可以使用
MPI_STATUS_IGNORE
,但这意味着你无法获取到消息的详细信息。 - 在使用
MPI_ANY_SOURCE
或MPI_ANY_TAG
接收消息时,你需要确保你的程序逻辑可以处理来自任何源或带有任何标签的消息。 - 使用
MPI_PROC_NULL
可以用于简化代码逻辑,比如在边界条件处理时,你可以使用它来避免编写特殊的边界检查代码。 - 在并行编程中,通配符的使用可能会影响性能,因为它可能导致不确定性和额外的开销。你应该在确保正确性的前提下,尽可能优化通信模式,减少对通配符的依赖。
MPI提供了强大的抽象来处理进程间的通信,但是它也要求程序员对通信模式有深入理解,以避免死锁和性能瓶颈。在使用这些通配符时,你应该仔细设计你的通信模式,并充分测试以确保程序的正确性和效率。
状态(MPI_Status *status)

实践:ping-pong通信
MPI(Message Passing Interface)是一种通信协议,用于编写并行计算程序。在并行计算中,"ping-pong"测试是一种简单的通信模式,用来衡量两个进程间通信的延迟。在这个测试中,一个进程(我们称之为"Ping")发送一个消息到另一个进程(我们称之为"Pong"),然后"Pong"进程接收这个消息并将其返回给"Ping"进程。
用MPI实现的ping-pong程序
#include <stdio.h>
#include <mpi.h>
int main(int argc, char *argv[]) {
int my_rank, num_procs, partner;
int ping_pong_count = 0;
int max_ping_pong_count = 10;
MPI_Status status;
// 初始化MPI环境
MPI_Init(&argc, &argv);
// 获取当前进程的排名
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
// 获取所有进程的数量
MPI_Comm_size(MPI_COMM_WORLD, &num_procs);
if (num_procs < 2) {
fprintf(stderr, "This test requires at least 2 processes\n");
MPI_Abort(MPI_COMM_WORLD, 1);
}
// 确定通信的伙伴进程的排名
partner = (my_rank + 1) % 2;
while (ping_pong_count < max_ping_pong_count) {
if (my_rank == ping_pong_count % 2) {
// 增加ping-pong计数器并发送消息
ping_pong_count++;
MPI_Send(&ping_pong_count, 1, MPI_INT, partner, 0, MPI_COMM_WORLD);
printf("%d sent and incremented ping_pong_count %d to %d\n", my_rank, ping_pong_count, partner);
} else {
// 接收消息
MPI_Recv(&ping_pong_count, 1, MPI_INT, partner, 0, MPI_COMM_WORLD, &status);
printf("%d received ping_pong_count %d from %d\n", my_rank, ping_pong_count, partner);
}
}
// 清理MPI环境
MPI_Finalize();
return 0;
}
代码解释
-
引入头文件:
#include <mpi.h>
是必须的,因为它包含了MPI程序所需的所有MPI函数和符号定义。 -
初始化MPI:
MPI_Init(&argc, &argv);
初始化MPI执行环境,这个函数必须在任何其他MPI函数之前调用。 -
获 取进程信息:
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
获取当前进程的排名(即进程号),MPI_Comm_size(MPI_COMM_WORLD, &num_procs);
获取参与运算的总进程数。 -
确认进程数量:这个程序至少需要两个进程来运行,如果进程数少于2,则程序会退出。
-
确定通信伙伴:每个进程都会找到它的通信伙伴,这里简单地通过计算排名的模2余数来确定。
-
Ping-Pong通信循环:使用
MPI_Send
和MPI_Recv
在两个进程间发送和接收消息。MPI_Send
用于发送消息,而MPI_Recv
用于接收消息。MPI_Status
结构体用于获取接收操作的状态信息。 -
打印消息:每次发送或接收消息后,进程会打印出相应的信息。
-
结束MPI:
MPI_Finalize();
结束MPI执行环境,这个函数必须在程序结束前调用。
注意:这个例子假设只有两个进程参与通信。在实际应用中,可能有多个进程,且通信模式可能更为复杂。此代码应该在支持MPI的环境中编译和运行,例如使用mpicc
编译器和mpirun
或mpiexec
来运行程序。
终止进程函数 MPI_Abort(MPI_COMM_WORLD, 1)
MPI_Abort(MPI_COMM_WORLD, 1)
是一个MPI函数,用于在发生错误时终止MPI程序的执行。这个函数会立即终止调用它的进程所在的所有MPI进程,并且尽可能地清理所有MPI状态。MPI_Abort
通常在无法恢复的错误发生时调用, 比如程序内部检测到了一个不可恢复的错误,或者用户想要在某个特定条件下提前结束程序。
函数参数解释:
-
第一个参数
MPI_COMM_WORLD
指定了要终止的通信器(communicator)。在这个例子中,它是MPI_COMM_WORLD
,意味着要终止所有与MPI_COMM_WORLD
关联的进程。MPI_COMM_WORLD
是一个预定义的通信器,包含了所有的MPI进程。 -
**第二个参数
1
是错误码,用于传递给程序外部,告知程序为什么被终止。**这个错误码可以被操作系统或其他监控软件用来确定程序终止的原因。在UNIX系统中,非零的退出码通常表示程序异常退出。
使用 MPI_Abort
函数时需要注意:
-
MPI_Abort
是一个集体操作,意味着它将影响所有在相同通信器中的进程。在这个例子中,所有的进程都会被终止,因为它们都是MPI_COMM_WORLD
的一部分。 -
由于
MPI_Abort
会立即终止所有相关进程,所以不会执行任何MPI进程的正常退出过程,比如不会调用MPI_Finalize
函数。因此,可能不会释放所有资源,也可能不会完成所有输出。 -
MPI_Abort
应该只在必要的时候使用,因为它不是一个优雅退出的方法。它不保证所有的进程都能接收到终止信号,也不保证终止的顺序。 -
通常,更偏好的做法是处理错误,尝试将程序恢复到一个稳定的状态,并让所有进程达成一致后再结束程序,而不是直接调用
MPI_Abort
。只有在错误无法恢复,且必须立即停止程序时,才应该使用MPI_Abort
。
阻塞通信和非 阻塞通信和缓冲发送
死锁
死锁(Deadlock)是计算机科学中的一个概念,特别是在并发控制、多线程和多进程编程中,指的是一种特定的阻塞状态,其中两个或多个运行的线程或进程都在等待对方停止,或等待某些无法由这些线程或进程控制的事件,结果是它们都无法进行下去。
以下是一个经典的死锁示例:
假设有两个进程(进程A和进程B)和两个资源(资源1和资源2):
- 进程A持有资源1并且等待资源2。
- 同时,进程B持有资源2并且等待资源1。
在这种情况下,没有任何进程能够继续执行,因为它们都在等待对方释放资源,从而形成了死锁。
为了防止死锁,可以使用多种方法,包括资源分配策略、锁顺序、死锁检测和恢复机制等。例如,确保程序以一致的顺序请求资源可以减少死锁的可能性,而死锁检测算法可以帮助系统识别和处理死锁。如果检测到死锁,系统可以采取各种措施来解决,例如撤销某些进程或强制释放资源。

概念
阻塞通信(Blocking Communication)
在阻塞通信中,进程在调用发送或接收操作时会被阻塞,直到某些条件得到满足。对于发送操作,阻塞可能意味着等待数据被复制到系统缓冲区,或者直到接收方接收数据。对于接收操作,阻塞意味着等待直到有数据到达并被复制到用户的缓冲区。
特点:
- 简单性:代码易于理解和维护,因为发送和接收操作完成后,进程才会继续执行。
- 确定性:当阻塞调用返回时,你知道数据已经发送或接收完毕。
示例:
MPI_Send(buffer, count, datatype, dest, tag, MPI_COMM_WORLD); // 发送操作
MPI_Recv(buffer, count, datatype, source, tag, MPI_COMM_WORLD, &status); // 接收操作
在这两个操作中,进程会等待,直到发 送或接收完成。
缓冲(Buffering)
在MPI中,发送消息的行为可以根据其对缓冲和阻塞的处理方式来区分。这些特性定义了发送操作如何与系统的缓冲区交互,以及发送操作在消息实际传递之前会不会阻塞调用进程。
缓冲
缓冲指的是消息在被发送和接收之间的临时存储。MPI实现通常有一个内部的缓冲区系统,它可以存储发送的消息,直到接收进程准备好接收它们。这个特性允许发送进程在接收进程实际调用接收操作之前就继续执行,从而可能提高并行程序的整体性能。
- 缓冲发送(Buffered Send):MPI提供了缓冲发送操作(例如
MPI_Bsend
),在这种模式下,发送的消息首先被复制到MPI的发送缓冲区中,然后发送进程就可以继续执行而不用等待接收进程开始接收。如果缓冲区空间不足以存储消息,发送操作可能会阻塞直到有足够的空间。
阻塞(Blocking)
阻塞指的是发送操作在消息被接收之前暂停(阻塞)调用进程的执行。在阻塞发送中,发送操作只有在满足特定条件后才会返回控制权给调用进程。
- 标准阻塞发送(Standard Blocking Send):
MPI_Send
是一个标准的阻塞发送操作。在这种模式下,发送操作可能会阻塞调用进程,直到消息数据被复制到系统缓冲区(如果存在)或者直到接收进程开始接收操作,从而确保发送进程可以安全地重用或修改发送缓冲区中的数据。
区别
- 缓冲区依赖性:缓冲发送依赖于MPI系统的 缓冲区,而标准发送可能不需要缓冲区或者使用更少的缓冲区。
- 阻塞行为:标准发送可能会阻塞进程直到发送操作完成(即数据被拷贝到缓冲区或者接收者开始接收),而缓冲发送会尝试立即返回,只有在没有足够缓冲区空间时才会阻塞。
- 资源使用:缓冲发送可能会消耗更多的缓冲资源,因为它需要在内部缓冲区中存储消息的副本。
- 程序复杂性:使用缓冲发送可能需要程序员管理缓冲区大小和可用性,增加了编程的复杂性。
在实际编程中,选择哪种发送方式取决于应用程序的需求、消息大小、通信模式和性能考量。标准发送简单直接,适用于大多数情况,但在高性能计算应用中,为了避免潜在的阻塞,可能需要更细致地控制消息的缓冲和发送方式。
缓冲发送(Buffered Send)和非阻塞通信(Non-blocking Communication)是两个不同的概念,尽管它们都旨在减少因通信导致的等待时间,但它们的工作方式有所不同。
缓冲发送
缓冲发送,如MPI_Bsend
,会将数据复制到MPI的内部缓冲区。如果缓冲区有足够的空间,发送操作会立即返回,即使接收方还没有开始接收消息。这意味着发送进程可以继续执行后续代码而不必等待接收进程已经开始接收。然而,如果内部缓冲区没有足够的空间来存储发送的消息,缓冲发送可能会阻塞。
非阻塞通信
非阻塞通信,如MPI_Isend
和MPI_Irecv
,指的是发送和接收操作启动后会立即返回,不管操作是否已经完成。这允许程序在消息实际传输的同时继续执行其他操作。非阻塞通信需要程序员在消息传输完成之前管理和测试通信请求(使用MPI_Test
或MPI_Wait
等函数)。
区别
- 缓冲发送:可能会阻塞,如果缓冲区不足。它依赖于MPI的内部缓冲区来存储消息副本。
- 非阻塞通信:始终立即返回,允许进程继续执行其他任务。它要求程序员管理通信状态,并在适当的时候确保通信已经完成。
总结来说,缓冲发送是一种尝试减少发送操作中阻塞的方式,但它不是真正的非阻塞通信。而非阻塞通信是一种允许并行操作和更细粒度控制的通信方式,它确保了发送和接收调用在开始后立即返回,让程序有机会在通信完成之前执行其他操作。
非阻塞通信(Non-blocking Communication)
非阻塞通信允许进程在调用发送或接收操作后继续执行,而不必等待操作完成。这意味着进程可以在数据被发送或接收的同时执行其他操作。
特点:
- 效率:可以在等待数据传输完成的同时执行计算,这有助于隐藏通信延迟。
- 复杂性:代码可能更难理解和维护,因为你必须管理多个同时发生的操作,并确保在使用数据之前完成通信。
示例:
MPI_Isend(buffer, count, datatype, dest, tag, MPI_COMM_WORLD, &request); // 非阻塞发送操作
MPI_Irecv(buffer, count, datatype, source, tag, MPI_COMM_WORLD, &request); // 非阻塞接收操作
// ... 执行其他操作 ...
MPI_Wait(&request, &status); // 等待非阻塞操作完成
在这里,MPI_Isend
和MPI_Irecv
调用后,进程可以立即继续执行代码,但必须在实际使用数据前调用MPI_Wait
来确保操作已经完成。
非阻塞通信:Isend、Irecv函数
MPI_Isend
和 MPI_Irecv
是 MPI (Message Passing Interface) 中用于非阻塞通信的两个函数。它们允许进程在不等待通信完成的情况下发起发送和接收操作,这样进程就可以继续执行其他任务,从而提高程序的并行性和效率。
MPI_Isend
MPI_Isend
用于开始一个非阻塞发送操作。它的函数原型如下:
int MPI_Isend(const void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request);
参数说明:
buf
:指向要发送数据的缓冲区的指针。count
:要发送的数据元素的数量。datatype
:发送的数据元素的类型。dest
:目标进程的秩(rank)。tag
:发送的消息标签,接收方将根据这个标签来接收 消息。comm
:使用的通信器。request
:MPI_Request
变量,用于后续的MPI_Wait
或MPI_Test
调用。
MPI_Irecv
MPI_Irecv
用于开始一个非阻塞接收操作。它的函数原型如下:
int MPI_Irecv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Request *request);
参数说明与 MPI_Isend
类似,不同之处在于 source
参数指定了消息来源的进程秩。
非阻塞通信的必要等待
在使用非阻塞通信时,无论是发送还是接收操作,都可能需要在某个时刻进行等待,但原因和时机可能有所不同:
-
非阻塞发送:发送操作通常需要等待的原因是确保数据已经被复制到系统缓冲区或者已经发送给接收方,这样发送方才能安全地重用或修改发送缓冲区中的数据。如果你在发送后立即修改了数据或者在发送完毕前退出了程序,可能会导致数据损坏或未定义的行为。
-
非阻塞接收:接收操作需要等待 的原因是确保接收缓冲区中的数据已经完整地到达,这样才能安全地读取和使用这些数据。
在MPI (Message Passing Interface) 中,MPI_Wait
和 MPI_Test
是用于等待或检查非阻塞通信操作完成的函数。这些函数与非阻塞发送 (MPI_Isend
) 和非阻塞接收 (MPI_Irecv
) 一起使用,以允许重叠计算与通信。
MPI_Wait
MPI_Wait
函数用于等待特定的非阻塞通信操作完成。
函数原型如下:
int MPI_Wait(MPI_Request *request, MPI_Status *status);
参数说明:
request
: 指向一个MPI_Request
类型的变量的指针,该变量在非阻塞操作时被赋值。MPI_Wait
会等待这个请求对应的操作完成。status
: 指向一个MPI_Status
结构的指针,用于存储操作完成后的状态信息。如果你对状态信息不感兴趣,可以传递MPI_STATUS_IGNORE
。
MPI_Wait
会阻塞调用线程直到对应的非阻塞操作完成。完成后,request
会被设置为 MPI_REQUEST_NULL
,表示请求对象可以被重用或释放。
MPI_Test
MPI_Test
函数用于检查特定的非阻塞通信操作是否已经完成,而不会阻塞调用线程。
函数原型如下:
int MPI_Test(MPI_Request *request, int *flag, MPI_Status *status);
参数说明:
request
: 同MPI_Wait
中的request
。flag
: 指向一个整数的指针,该函数会将其设置为非零值,如果对应的操作已经完成;否则设置为零。status
: 同MPI_Wait
中的status
。
如果操作已经完成,MPI_Test
会设置 flag
为非零值,并且 request
会被设置为 MPI_REQUEST_NULL
。如果操作尚未完成,flag
会被设置为零,且 request
保持不变。
示例
下面是一个简单的例子,展示了如何使 用 MPI_Isend
、MPI_Irecv
、MPI_Wait
和 MPI_Test
:
#include <mpi.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
MPI_Init(&argc, &argv);
int world_rank;
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
int world_size;
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
const int TAG = 0;
MPI_Request request;
MPI_Status status;
if (world_size < 2) {
fprintf(stderr, "World size must be greater than 1 for %s\n", argv[0]);
MPI_Abort(MPI_COMM_WORLD, 1);
}
int number;
if (world_rank == 0) {
number = -1;
MPI_Isend(&number, 1, MPI_INT, 1, TAG, MPI_COMM_WORLD, &request);
MPI_Wait(&request, &status); // Wait for the send to complete
printf("Process 0 sent number %d to process 1\n", number);
} else if (world_rank == 1) {
MPI_Irecv(&number, 1, MPI_INT, 0, TAG, MPI_COMM_WORLD, &request);
int flag = 0;
while (!flag) {
MPI_Test(&request, &flag, &status); // Test for the receive
// Perform other work here...
}
printf("Process 1 received number %d from process 0\n", number);
}
MPI_Finalize();
return 0;
}
在这个例子中,进程 0 发送一个数字给进程 1。进程 0 使用 MPI_Isend
和 MPI_Wait
来发送数字,并等待发送完成。进程 1 使用 MPI_Irecv
和 MPI_Test
来接收数字,并且在等待过程中可以执行其他工作。注意,实际的应用中,MPI_Test
循环中应包含一些有用的计算或处理,以避免忙等待。
示例
下面的示例代码展示了如何使用 MPI_Isend
和 MPI_Irecv
:
#include <stdio.h>
#include <mpi.h>
int main(int argc, char* argv[]) {
MPI_Init(&argc, &argv);
int world_rank;
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
int world_size;
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
const int TAG = 0;
MPI_Request request;
MPI_Status status;
if (world_size < 2) {
fprintf(stderr, "World size must be greater than 1 for %s\n", argv[0]);
MPI_Abort(MPI_COMM_WORLD, 1);
}
int number;
if (world_rank == 0) {
number = -1;
MPI_Isend(&number, 1, MPI_INT, 1, TAG, MPI_COMM_WORLD, &request);
// 在这里可以执行其他任务
MPI_Wait(&request, &status); // 等待发送完成
} else if (world_rank == 1) {
MPI_Irecv(&number, 1, MPI_INT, 0, TAG, MPI_COMM_WORLD, &request);
// 在这里可以执行其他任务
MPI_Wait(&request, &status); // 等待接收完成
printf("Process 1 received number %d from process 0\n", number);
}
MPI_Finalize();
return 0;
}
在这个示例中,进程 0 使用 MPI_Isend
发送一个整数给进程 1,而进程 1 使用 MPI_Irecv
来接收这个整数。发送和接收操作都是非阻塞的,但我们在这里使用 MPI_Wait
来确保在程序结束前通信操作已经完成。在实际应用中,你可能会在调用 MPI_Wait
之前执行一些与通信无关的计算,以此来隐藏通信延迟。