Example-3-Using MPI_Sendrecv

本文通过示例介绍了如何使用MPI_Sendrecv函数来替代并行计算中的连续发送和接收操作,从而简化代码。在例子中,4个进程互相交换数据,每个进程与相邻的两个进程通信,MPI_Sendrecv确保了数据的正确传递,避免了死锁。

例2中,每个进程既接收又发送数据,可以利用MPI_Sendrecv函数进行简化,此例和例2功能完全相同,首先看MPI_Sendrecv的用法

代码如下:

MPI_Sendrecv(
void *sendbuf //initial address of send buffer
int sendcount //number of entries to send
MPI_Datatype sendtype //type of entries in send buffer
int dest //rank of destination
int sendtag //send tag
void *recvbuf //initial address of receive buffer
int recvcount //max number of entries to receive
MPI_Datatype recvtype //type of entries in receive buffer
int source //rank of source
int recvtag //receive tag
MPI_Comm comm //group communicator
MPI_Status status //return status;

Note there is compatibility between send-receive and normal sends and receives. A message sent by a send-receive can be received by a regular receive or probed by a regular probe, and a send-receive can receive a message sent by a regular send. MPI_Sendrecv excutes a blocking send and receive operation. both the sent and the receive use the same communicator, but have distinct tag argument. the send buffer and receive buffers must be disjoint, and may have different lengths and datatypes.
Simply speaking: MPI_Sendrecv can replace a pair of consecutive send and receive calls originating from a single process.

Example:

!--Exchange messages
     tag1=1
     tag2=2
     if (myid == 0){
      MPI_Sendrecv(a,1,mpi_real,1,tag1,
    &              b,1,mpi_real,1,tag2,
    &            MPI_COMM_WORLD, status) //Rank 0 send message to 1 and receive message from 1 simultaneously.
    }
     else if (myid == 1) {
      MPI_Sendrecv(b,1,mpi_real,0,tag2,
    &              a,1,mpi_real,0,tag1,
    &             MPI_COMM_WORLD,status)////Rank 1 send message to 0 and receive message from 0 simultaneously.
    }

现在来看一个更复杂一点的例子:设4个进程,每个进程上有四个数组,所有数组初始值等于进程号。然后每个进程和它相邻的两个进程交换数据。具体来说,
For Rank=0:
A0={0},A1={0},A2={0},A3={0};
For Rank=1:
A0={1},A1={1},A2={1},A3={1};
For Rank=2:
A0={2},A1={2},A2={2},A3={2};
For Rank=3:
A0={3},A1={3},A2={3},A3={3};
然后进程i发送A0和A1到i+1和i-1;并且接收i-1和i+1发送的数据存储在A2和A3.
代码如下:

#include<stdio.h>
#include "mpi.h"
#include <cstdio>
<
<think>我们讨论的是Fortran中MPI_Send和MPI_Recv的使用方法。根据引用[1][3][4]中提供的信息,特别是关于MPI_Sendrecv的说明,但用户明确要求使用MPI_Send和MPI_Recv。在Fortran中使用MPI_Send和MPI_Recv的基本步骤:1.初始化MPI环境(MPI_Init)2.获取当前进程的rank和总进程数(MPI_Comm_rank,MPI_Comm_size)3.根据rank决定发送接收行为(通常用一个进程发送,另一个进程接收)4.使用MPI_Send发送消息5.使用MPI_Recv接收消息(注意:发送接收的tag要匹配,通信器要一致)6.最后终止MPI环境(MPI_Finalize)注意:MPI_Send和MPI_Recv是阻塞操作。如果不小心设计通信模式,容易导致死锁。例如,如果两个进程同时先发送接收,则可能双方都在等待对方接收而无法继续,形成死锁。此时可以考虑使用MPI_Sendrecv(如引用[3][4]所述)或非阻塞通信。下面我们给出一个简单的Fortran示例:两个进程(rank0和rank1)之间交换一个整数和一个实数数组。注意:实际代码中需要错误检查(ierr),这里为了简洁省略。示例代码:```fortranprogramsend_recv_exampleusempiimplicitnoneinteger::ierr,rank,sizeinteger::send_tag=100,recv_tag=100integer,parameter::num_ints=1,num_reals=5integer::status(MPI_STATUS_SIZE)integer::send_int,recv_intreal::send_real_array(num_reals),recv_real_array(num_reals)integer::dest,source!初始化MPIcallMPI_Init(ierr)callMPI_Comm_rank(MPI_COMM_WORLD,rank,ierr)callMPI_Comm_size(MPI_COMM_WORLD,size,ierr)!检查进程数是否为2if(size<2)thenif(rank==0)print*,&#39;Thisexamplerequiresatleast2processes.&#39;callMPI_Finalize(ierr)stopendif!初始化要发送的数据send_int=rank*10!每个进程发送不同的整数值send_real_array=real(rank)!数组每个元素设置为当前进程的rank(实数)!设置目标进程:进程0发送给进程1,进程1发送给进程0if(rank==0)thendest=1source=1elsedest=0source=0endif!开始通信:注意,如果两个进程都先发送,可能死锁。所以我们让进程0先发送接收,进程1先接收发送。if(rank==0)then!进程0先发送整数,然后发送实数数组callMPI_Send(send_int,num_ints,MPI_INTEGER,dest,send_tag,MPI_COMM_WORLD,ierr)callMPI_Send(send_real_array,num_reals,MPI_REAL,dest,send_tag+1,MPI_COMM_WORLD,ierr)!然后接收来自进程1的整数和实数数组callMPI_Recv(recv_int,num_ints,MPI_INTEGER,source,recv_tag,MPI_COMM_WORLD,status,ierr)callMPI_Recv(recv_real_array,num_reals,MPI_REAL,source,recv_tag+1,MPI_COMM_WORLD,status,ierr)else!进程1先接收来自进程0的整数和数组callMPI_Recv(recv_int,num_ints,MPI_INTEGER,source,send_tag,MPI_COMM_WORLD,status,ierr)callMPI_Recv(recv_real_array,num_reals,MPI_REAL,source,send_tag+1,MPI_COMM_WORLD,status,ierr)!然后发送自己的整数和数组callMPI_Send(send_int,num_ints,MPI_INTEGER,dest,recv_tag,MPI_COMM_WORLD,ierr)callMPI_Send(send_real_array,num_reals,MPI_REAL,dest,recv_tag+1,MPI_COMM_WORLD,ierr)endif!打印接收到的数据print*,&#39;Rank&#39;,rank,&#39;receivedinteger:&#39;,recv_int,&#39;andrealarray:&#39;,recv_real_arraycallMPI_Finalize(ierr)endprogramsend_recv_example```代码解释:-该程序需要恰好两个进程(或至少两个,但只有rank0和rank1会进行通信)。-我们定义了一个整数和一个实数数组作为发送数据。每个进程将自己的rank乘以10作为整数,并将一个实数数组的所有元素设置为当前rank(实数)。-进程0先发送两个消息(一个整数和一个数组)给进程1,然后等待接收进程1发来的消息。-进程1则先等待接收进程0的消息,然后再发送自己的消息。-这样安排通信顺序避免了死锁:进程0先发送接收,进程1先接收发送-注意:我们使用了两个不同的消息标签(tag)来区分整数和数组消息(send_tag和send_tag+1)。接收时使用相应的标签。编译和运行:```bashmpifort-osend_recv_examplesend_recv_example.f90mpirun-n2./send_recv_example```输出可能如下:```Rank0receivedinteger:10andrealarray:1.01.01.01.01.0Rank1receivedinteger:0andrealarray:0.00.00.00.00.0```注意事项:1.如果通信顺序不当,比如两个进程都先执行发送操作,则会发生死锁。因为双方都在等待对方接收消息,而双方又都卡在发送操作(阻塞发送需要对方开始接收才能完成)。所以必须设计好通信顺序,或者使用非阻塞通信(MPI_Isend,MPI_Irecv)或组合操作(如MPI_Sendrecv)。2.消息标签(tag)和通信器(comm)必须匹配。同一个通信中,发送方和接收方要使用相同的标签和通信器。3.MPI_Recv中的status参数(在Fortran中是整型数组,大小至少为MPI_STATUS_SIZE)可以用来获取消息的详细信息,如实际接收到的数据数量、发送者rank等。4.数据类型要匹配:发送整数数组用MPI_INTEGER,发送实数数组用MPI_REAL(或根据实际情况选择,如双精度用MPI_DOUBLE_PRECISION)。如果需要在多个进程间进行更复杂的通信,请务必小心设计以避免死锁,或者考虑使用非阻塞通信或集体通信操作(如引用[2]中的MPI_Gather)[^2]。###相关问题1.如何避免在MPI_Send和MPI_Recv中发生死锁?2.如何在Fortran中使用MPI_Sendrecv函数避免死锁?(如引用[3][4]所述)3.MPI_Isend和MPI_Irecv非阻塞通信的使用方法?4.如何在多对多通信中使用MPI_Send和MPI_Recv?</think> 下面详细介绍在Fortran中使用MPI_Send和MPI_Recv进行消息传递的方法,结合您提供的最新引用信息。 ### Fortran中MPI_Send和MPI_Recv使用方法 #### 1. **函数原型** ```fortran ! MPI_Send (发送消息) call MPI_Send(sendbuf, sendcount, sendtype, dest, sendtag, comm, ierr) ! MPI_Recv (接收消息) call MPI_Recv(recvbuf, recvcount, recvtype, source, recvtag, comm, status, ierr) ``` **参数说明**: | 参数 | 类型 | 说明 | |-------------|-----------------|-----------------------------------------------------------------------------------------| | `sendbuf` | 任意类型 | 发送数据缓冲区起始地址 | | `sendcount` | INTEGER | 发送数据个数 | | `sendtype` | MPI_Datatype | 发送数据类型(如`MPI_INTEGER`, `MPI_REAL`, `MPI_DOUBLE_PRECISION`) | | `dest` | INTEGER | **目标进程rank号** (0到size-1) | | `sendtag` | INTEGER | **发送消息标签** (0-32767),用于标识消息类型 | | `comm` | MPI_Comm | 通信器(通常用`MPI_COMM_WORLD`) | | `ierr` | INTEGER | 错误返回码 | | `recvbuf` | 任意类型 | 接收数据缓冲区 | | `recvcount` | INTEGER | **最大接收数据个数** (实际接收数可通过status获取) | | `source` | INTEGER | **源进程rank号** (`MPI_ANY_SOURCE`表示接收任意源) | | `recvtag` | INTEGER | **接收消息标签** (`MPI_ANY_TAG`表示接收任意标签) | | `status` | INTEGER数组 | 返回状态信息,数组大小至少为`MPI_STATUS_SIZE` (包含实际接收数、源rank等信息) [^1][^4] | #### 2. **基本通信流程** ```mermaid flowchart TD A[进程A] -->|MPI_Send| B[进程B] B[进程B] -->|MPI_Recv| A[进程A] ``` #### 3. **完整示例代码** ```fortran program basic_send_recv use mpi implicit none integer :: ierr, rank, size, dest, source, send_tag = 100 integer, parameter :: buffer_size = 5 real :: send_buffer(buffer_size), recv_buffer(buffer_size) integer :: status(MPI_STATUS_SIZE) ! 初始化MPI环境 call MPI_Init(ierr) call MPI_Comm_rank(MPI_COMM_WORLD, rank, ierr) call MPI_Comm_size(MPI_COMM_WORLD, size, ierr) ! 检查进程数 if (size < 2) then if (rank == 0) print*, &#39;需要至少2个进程&#39; call MPI_Finalize(ierr) stop end if ! 初始化发送数据 (每个进程数据不同) send_buffer = rank * 10.0 + [(i, i = 1, buffer_size)] ! 确定通信对象 if (rank == 0) then dest = 1 ! rank0发送到rank1 source = 1 ! rank0从rank1接收 else dest = 0 ! rank1发送到rank0 source = 0 ! rank1从rank0接收 end if ! 通信阶段 (注意避免死锁) if (rank == 0) then call MPI_Send(send_buffer, buffer_size, MPI_REAL, dest, send_tag, MPI_COMM_WORLD, ierr) print*, &#39;Rank&#39;, rank, &#39;: 已发送数据&#39;, send_buffer call MPI_Recv(recv_buffer, buffer_size, MPI_REAL, source, send_tag, MPI_COMM_WORLD, status, ierr) print*, &#39;Rank&#39;, rank, &#39;: 接收到数据&#39;, recv_buffer else call MPI_Recv(recv_buffer, buffer_size, MPI_REAL, source, send_tag, MPI_COMM_WORLD, status, ierr) print*, &#39;Rank&#39;, rank, &#39;: 接收到数据&#39;, recv_buffer call MPI_Send(send_buffer, buffer_size, MPI_REAL, dest, send_tag, MPI_COMM_WORLD, ierr) print*, &#39;Rank&#39;, rank, &#39;: 已发送数据&#39;, send_buffer end if call MPI_Finalize(ierr) end program basic_send_recv ``` #### 4. **关键注意事项** 1. **标签匹配** 发送接收操作的标签必须匹配(上例中均使用`send_tag=100`) 2. **死锁避免** - 示例中采用**交替通信顺序**(rank0先发后收,rank1先收后发) - 替代方案:使用`MPI_Sendrecv`(引用[3][4])或非阻塞通信`MPI_Isend`/`MPI_Irecv` 3. **缓冲区大小** `recvcount`应 >= 实际接收数据量,通过`status`获取实际接收数: ```fortran integer :: actual_count call MPI_Get_count(status, MPI_REAL, actual_count, ierr) ``` 4. **通配符使用** ```fortran ! 接收任意源、任意标签的消息 call MPI_Recv(buf, count, datatype, MPI_ANY_SOURCE, MPI_ANY_TAG, comm, status, ierr) ``` #### 5. **编译执行** ```bash # 编译 mpifort -o send_recv basic_send_recv.f90 # 运行 (2个进程) mpirun -np 2 ./send_recv ``` **输出示例**: ``` Rank 0 : 已发送数据 [10.0, 11.0, 12.0, 13.0, 14.0] Rank 1 : 接收到数据 [10.0, 11.0, 12.0, 13.0, 14.0] Rank 1 : 已发送数据 [20.0, 21.0, 22.0, 23.0, 24.0] Rank 0 : 接收到数据 [20.0, 21.0, 22.0, 23.0, 24.0] ``` ### 死锁问题深度分析 当进程间存在复杂依赖时,单纯使用`MPI_Send`/`MPI_Recv`容易导致死锁。此时应考虑: 1. **使用MPI_Sendrecv** ```fortran call MPI_Sendrecv(sendbuf, sendcount, sendtype, dest, sendtag, & recvbuf, recvcount, recvtype, source, recvtag, & comm, status, ierr) ``` 此函数将发送接收合并为原子操作,避免顺序依赖导致的死锁[^3][^4] 2. **非阻塞通信** 使用`MPI_Isend`和`MPI_Irecv`配合`MPI_Wait`: ```fortran integer :: request(2) call MPI_Isend(sendbuf, sendcount, sendtype, dest, sendtag, comm, request(1), ierr) call MPI_Irecv(recvbuf, recvcount, recvtype, source, recvtag, comm, request(2), ierr) call MPI_Waitall(2, request, MPI_STATUSES_IGNORE, ierr) ``` ### 消息传递与数据收集的对比 | 操作 | 特点 | 适用场景 | |--------------|------------------------------------|-------------------------| | `MPI_Send/Recv` | 点对点通信 | 任意两进程间数据交换 | | `MPI_Gather` | 收集**相同长度**数据块到根进程 | 汇总计算结果[^2] | | `MPI_Gatherv` | 收集**不同长度**数据块 | 非均匀数据收集[^2] |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值