MPI_Sendrecv
是 MPI 中用于一次调用同时发送和接收消息的函数。这一函数常用于需要在进程间交换数据的场景。与单独使用 MPI_Send
和 MPI_Recv
相比,MPI_Sendrecv
能有效避免死锁问题,因为它确保发送和接收的操作是同步的。
MPI_Sendrecv
函数原型
int MPI_Sendrecv(
const void *sendbuf, // 发送缓冲区的起始地址
int sendcount, // 要发送的数据项个数
MPI_Datatype sendtype,// 发送数据的类型
int dest, // 发送目标进程的 rank
int sendtag, // 发送消息的 tag
void *recvbuf, // 接收缓冲区的起始地址
int recvcount, // 要接收的数据项个数
MPI_Datatype recvtype,// 接收数据的类型
int source, // 接收消息的进程 rank
int recvtag, // 接收消息的 tag
MPI_Comm comm, // 通信域
MPI_Status *status // 保存接收消息的状态
);
参数解释
-
*
sendbuf
(const void)**:指向发送缓冲区的指针。它包含将要发送的数据。如果你发送一个数组,这里传递的是数组的起始地址。 -
sendcount
(int):要发送的数据项的个数。指定发送缓冲区中包含的项目数。例如,如果发送一个包含 100 个整数的数组,sendcount
应设为 100。 -
sendtype
(MPI_Datatype):发送数据的类型。常见的类型包括MPI_INT
、MPI_FLOAT
、MPI_DOUBLE
等,表示你发送的数据的类型。 -
dest
(int):发送的目标进程的rank
(进程编号)。你可以指定将消息发送给哪个进程。 -
sendtag
(int):发送消息的标识符。tag
用于区分不同的消息。在发送方和接收方,tag
必须匹配。 -
*
recvbuf
(void)**:指向接收缓冲区的指针。它指定接收消息时存储数据的地方。 -
recvcount
(int):要接收的数据项个数。指定接收缓冲区中可容纳的项目数量。接收方缓冲区大小应足够大以容纳消息。 -
recvtype
(MPI_Datatype):接收数据的类型,与发送方发送的数据类型一致。可以是MPI_INT
、MPI_FLOAT
、MPI_DOUBLE
等。 -
source
(int):指定接收消息的进程rank
,表示从哪个进程接收数据。可以是具体的进程rank
,或者使用MPI_ANY_SOURCE
来接收来自任意进程的消息。 -
recvtag
(int):接收消息的tag
,用于与发送消息的tag
匹配。可以指定具体的tag
,或者使用MPI_ANY_TAG
来接收任何tag
的消息。 -
comm
(MPI_Comm):通信域,指定消息发送和接收的进程组,常用的通信域是MPI_COMM_WORLD
,表示所有 MPI 进程的全局通信域。 -
*
status
(MPI_Status)**:用于保存接收到消息的状态。接收完成后可以通过status
获取消息的来源、tag
等信息。如果不需要状态信息,可以传递MPI_STATUS_IGNORE
。
工作机制
-
MPI_Sendrecv
是阻塞的,即在调用过程中,发送和接收操作会同步完成。它能避免两个进程在互相等待彼此的消息时发生死锁问题,因为发送和接收是在同一个操作中进行的。 -
MPI_Sendrecv
在内部顺序执行发送和接收操作,确保消息的顺利传递。
例子
假设有两个进程(rank 0
和 rank 1
),它们互相交换数据。进程 0 发送数据给进程 1,同时接收来自进程 1 的数据。进程 1 也执行相同操作。
#include "mpi.h"
#include <stdio.h>
int main(int argc, char *argv[]) {
int myid, numprocs, send_data, recv_data;
MPI_Status status;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myid);
MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
// 初始化发送数据
if (myid == 0) {
send_data = 100; // 进程 0 要发送的值
} else if (myid == 1) {
send_data = 200; // 进程 1 要发送的值
}
// 进程 0 和进程 1 互相交换数据
if (myid == 0) {
MPI_Sendrecv(&send_data, 1, MPI_INT, 1, 0, // 发送给 rank 1
&recv_data, 1, MPI_INT, 1, 0, // 从 rank 1 接收
MPI_COMM_WORLD, &status);
printf("Process %d sent %d and received %d\n", myid, send_data, recv_data);
} else if (myid == 1) {
MPI_Sendrecv(&send_data, 1, MPI_INT, 0, 0, // 发送给 rank 0
&recv_data, 1, MPI_INT, 0, 0, // 从 rank 0 接收
MPI_COMM_WORLD, &status);
printf("Process %d sent %d and received %d\n", myid, send_data, recv_data);
}
MPI_Finalize();
return 0;
}
输出示例:
Process 0 sent 100 and received 200
Process 1 sent 200 and received 100
注意事项
-
同步性:
MPI_Sendrecv
是同步阻塞的,因此它可以防止进程之间的死锁问题。这是因为它将发送和接收操作组合在一起,在发送或接收操作未完成之前不会继续执行。 -
消息匹配:
sendtag
和recvtag
必须匹配。只有当发送和接收双方的tag
和进程号都匹配时,消息才会被成功接收。 -
进程拓扑:
MPI_Sendrecv
常用于进程拓扑结构的通信中,例如网格、环形等。它允许每个进程同时向一个方向发送消息,并从另一个方向接收消息。 -
状态查询:可以使用
status
来检查接收到的消息的状态,包括消息的来源、tag
等信息。如果不关心接收的状态,可以使用MPI_STATUS_IGNORE
作为status
参数。 -
接受的数据类型可以与发送端的不同
-
接受的数据项的个数可以与发送端的数据项的个数不同,接收大小不能小于发送大小,因为这会导致写入超过缓冲区的上限而产生错误。