Example-2-MPI_Scatterv,MPI_Send,MPI_Recv

本文通过一个具体的示例,介绍了如何使用MPI实现进程间的初始数据分散及后续的数据交换过程,包括数据发送与接收的具体步骤。

此例将例1扩展一点,根进程分散数据后,进程间再进行数据交换,具体来说,第i个进程发送一定量的数据到第i+1和第i-1个进程。注意每个进程要更新当前数据,数据交换过程如下图所示:

这里写图片描述

代码如下:

#include "mpi.h"
#include <cstdio>
#include <math.h>
#include <iostream>
#include <vector>
#include <stdlib.h>

#define np 203  //number of elements need to be scattered at root
const int MASTER = 0; // Rank of of the master process

int main(int argc, char* argv[])
{
    std::vector<double> PosX(np);
    std::vector<double> RecPosX;
    int nProcs , Rank ;//number of processes, and id of rank
    int i,j;
    int reminder; //reminder if it is not divisible
    double StartTime,EndTime; //timing
    MPI_Status status;

    MPI_Init ( & argc , & argv );
    MPI_Comm_size ( MPI_COMM_WORLD , & nProcs );
    MPI_Comm_rank ( MPI_COMM_WORLD , & Rank );

    StartTime = MPI_Wtime(); //start timing

    //initialize array
    int* sendcounts = NULL ;
    int* displs = NULL ;
    int* TotLocalNum;

    int* numsendright;
    int* numsendleft;

    double *sendbufright,*recvbufright;
    double *sendbufleft,*recvbufleft;
    int rightrank,leftrank;

    //Initialize the data
    if ( Rank == MASTER )
    {
        for(i=0;i<np;i++)
        {
            //PosX.push_back(i);
            //PosX.resize(np,-1);
            PosX[i] = i * 1.0;

        }
    }

    sendcounts = new int [ nProcs ]; //allocate memory for array storing number of elements
    numsendright = new int[ nProcs ];
    numsendleft = new int[ nProcs ];
    TotLocalNum = new int[ nProcs ]; //store the total number in each process

    reminder = np%nProcs; //remineder

    //calculate number of elements need to be scattered in each process
    for(i=0;i<nProcs;i++)
    {
        sendcounts [i] = int(1.0*np/nProcs); 
    }
    //number of elements in the last process
        sendcounts [nProcs-1] = sendcounts [nProcs-1] + reminder;
    //calculate corresponding displacement  
    //std::cout<<sendcounts[Rank]<<std::endl;   
    displs = new int [ nProcs ];
    for(i=0;i<nProcs;i++)
        {
            displs[i] = 0;
            for(j=0;j<i;j++)
            {
                displs[i] = displs[i] + sendcounts[j];
            }
        }
        //allocate the receive buffer   
    for(i=0;i<sendcounts[Rank];i++)
    {
        RecPosX.push_back(i);
        RecPosX.resize(sendcounts[Rank],-100);
    }       
        MPI_Scatterv(&PosX[0], sendcounts, displs, MPI_DOUBLE, 
                     &RecPosX[0], sendcounts[Rank], MPI_DOUBLE, 0, MPI_COMM_WORLD);

    //output results after MPI_Scatterv operation
    std::cout<<"My Rank = "<<Rank<<" And data I received are: "<<sendcounts[Rank]<<std::endl;
    for(i=0;i<sendcounts[Rank];i++)
    {
        RecPosX[i] = RecPosX[i] + Rank * 10000;
        std::cout<<RecPosX[i]<<"  ";
        if((i+1) %10 == 0)std::cout<<std::endl;
    }
        std::cout<<std::endl;
    std::cout<<"Now Sending Data to the RIGHT neighboring process......"<<std::endl;
    //specify how many elements will be sent or received in each process
        for(i=0;i<nProcs;i++)
        {
            numsendright[i] = i + 5;
            numsendleft[i] = i + 4;
        }
        //Allocate the memory
        sendbufright = (double *)malloc(numsendright[Rank] * sizeof(double));
        recvbufright = (double *)malloc(numsendright[Rank] * sizeof(double));
        sendbufleft = (double *)malloc(numsendleft[Rank] * sizeof(double));
        recvbufleft = (double *)malloc(numsendleft[Rank] * sizeof(double));
   //randomly specify the data needs to be sent to the right neighbor
        int id;
        for(i=0;i<numsendright[Rank];i++)
        {
            id = rand() % sendcounts[Rank];
            sendbufright[i] = RecPosX[id];
            RecPosX.erase(RecPosX.begin()+id);
            sendcounts[Rank]=sendcounts[Rank]-1;
        }

        TotLocalNum[Rank] = sendcounts[Rank];

        rightrank = (Rank + 1) % nProcs;
        leftrank = (Rank + nProcs-1)%nProcs;

        //Each process sends data to its right process
        MPI_Send(sendbufright,numsendright[Rank],MPI_DOUBLE,rightrank,99,MPI_COMM_WORLD);

        std::cout<<"Rank= "<<Rank<<" SEND "<<numsendright[Rank]<<" Data To Rank= "<<rightrank<<std::endl;
        for(i=0;i<numsendright[Rank];i++)
        {
            std::cout<<i<<'\t'<<sendbufright[i]<<std::endl;
        }
        //Each process receives data from its left process
        MPI_Recv(recvbufright,numsendright[leftrank],MPI_DOUBLE,leftrank,99,MPI_COMM_WORLD,&status);

        sendcounts[Rank]  = sendcounts[Rank] + numsendright[leftrank];
        TotLocalNum[Rank] = TotLocalNum[Rank] + numsendright[leftrank];

        std::cout<<std::endl;
        std::cout<<"Rank= "<<Rank<<" RECEIVE "<<numsendright[leftrank]<<" Data From Rank= "<<leftrank<<std::endl;
        for(i=0;i<numsendright[leftrank];i++)
        {
            std::cout<<i<<'\t'<<recvbufright[i]<<std::endl;
        }

        //insert the received data here
        for(i=0;i<numsendright[leftrank];i++)
        {
            RecPosX.insert(RecPosX.end(),recvbufright[i]);
        }

        //MPI_Barrier(MPI_COMM_WORLD);
        std::cout<<std::endl;
        std::cout<<"Total number in Rank= "<<Rank<<" is "<<TotLocalNum[Rank]<<std::endl;
        for(i=0;i<TotLocalNum[Rank];i++)
        {
            std::cout<<RecPosX[i]<<"  ";
            if((i+1) %10 == 0)std::cout<<std::endl;
        }

        ///////////////////////////Start to send data to the left neighbor//////////////////////
        //Step 1: Prepare data need to be sent to the left neighboring process
        //randomly specify the data 
        std::cout<<std::endl;
        std::cout<<"Now Sending Data to the LEFT neighboring process......"<<std::endl;
        for(i=0;i<numsendleft[Rank];i++)
        {
            id = rand() % sendcounts[Rank];
            sendbufleft[i] = RecPosX[id];
            RecPosX.erase(RecPosX.begin()+id);
            sendcounts[Rank]=sendcounts[Rank]-1;
        }
        TotLocalNum[Rank] = sendcounts[Rank]; //Updating local number of data
        //Step 2
        //Each process sends data to its left process
        MPI_Send(sendbufleft,numsendleft[Rank],MPI_DOUBLE,leftrank,199,MPI_COMM_WORLD);

        std::cout<<"Rank= "<<Rank<<" SEND "<<numsendleft[Rank]<<" Data To Rank= "<<leftrank<<std::endl;
        for(i=0;i<numsendleft[Rank];i++)
        {
            std::cout<<i<<'\t'<<sendbufleft[i]<<std::endl;
        }
        //Each process receives data from its left process
        MPI_Recv(recvbufleft,numsendleft[rightrank],MPI_DOUBLE,rightrank,199,MPI_COMM_WORLD,&status);
        sendcounts[Rank]  = sendcounts[Rank] + numsendleft[rightrank];
        TotLocalNum[Rank] = TotLocalNum[Rank] + numsendleft[rightrank];
        std::cout<<std::endl;
        std::cout<<"Rank= "<<Rank<<" RECEIVE "<<numsendleft[rightrank]<<" Data From Rank= "<<rightrank<<std::endl;
        for(i=0;i<numsendleft[rightrank];i++)
        {
            std::cout<<i<<'\t'<<recvbufleft[i]<<std::endl;
        }

        //insert the received data here
        for(i=0;i<numsendleft[rightrank];i++)
        {
            RecPosX.insert(RecPosX.end(),recvbufleft[i]);
        }

        std::cout<<std::endl;

        std::cout<<"Total number in Rank= "<<Rank<<" is "<<TotLocalNum[Rank]<<std::endl;
        for(i=0;i<TotLocalNum[Rank];i++)
        {
            std::cout<<RecPosX[i]<<"  ";
            if((i+1)%10 == 0)std::cout<<std::endl;
        }

    EndTime = MPI_Wtime();
    std::cout<<std::endl;
    if(Rank==MASTER)
    {
            std::cout<<std::endl;
            std::cout<<"Total Time Spending For: "<<nProcs<<" Is "<<EndTime-StartTime<<" :Second"<<std::endl;
    }
    free(sendbufright);
    free(recvbufright);
    free(sendbufleft);
    free(recvbufleft);
    MPI_Finalize();
    return 0; 

}

设初始有203个数据,总共4个进程,以进程3为例,开始进程3得到53个数据:
30150————————————–30159
30160————————————–30169
30170————————————–30179
30180————————————–30189
30190————————————–30199
30200 30201 30202

然后进程3发送8个数据给进程0,随机抽取8个数据为:
第0个————30191
第1个————30157
第2个————30161
第3个————30150
第4个————30163
第5个————30182
第6个————30164
第7个————30165
进程3同时从进程2收到7个数据:
第0个————20141
第1个————20144
第2个————20148
第3个————20139
第4个————20133
第5个————20119
第6个————20142

此后,进程3共有52个数据:
30151…………………………………………………30162
30166…………………………………………………30175
30176…………………………………………………30186
30187…………………………………………………30197
30198…………………………………………………20133
20119 20142

然后开始向i-1进程发送数据,3号进程给2号发送7个数据:
第0个————30183
第1个————30194
……………………………………..
第6个————30187

然后3号进程从0号收到4个数据:
第0个————37
第1个————42
第0个————34
第1个————12

最后进程3上共有49个数据:

30151————————————-30168
30169————————————-30179
30180————————————-30193
30195————————————-20144
20148 20139——–37 42 34 12

执行完毕。
输出如图:
这里写图片描述

<think>我们讨论的是Fortran中MPI_SendMPI_Recv的使用方法。根据引用[1][3][4]中提供的信息,特别是关于MPI_Sendrecv的说明,但用户明确要求使用MPI_SendMPI_Recv。在Fortran中使用MPI_SendMPI_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_SendMPI_Recv是阻塞操作。如果不小心设计通信模式,容易导致死锁。例如,如果两个进程同时先发送接收,则可能双方都在等待对方接收而无法继续,形成死锁。此时可以考虑使用MPI_Sendrecv(如引用[3][4]所述)或非阻塞通信。下面我们给出一个简单的Fortran示例:两个进程(rank0rank1)之间交换一个整数一个实数数组。注意:实际代码中需要错误检查(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*,'Thisexamplerequiresatleast2processes.'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*,'Rank',rank,'receivedinteger:',recv_int,'andrealarray:',recv_real_arraycallMPI_Finalize(ierr)endprogramsend_recv_example```代码解释:-该程序需要恰好两个进程(或至少两个,但只有rank0rank1会进行通信)。-我们定义了一个整数一个实数数组作为发送数据。每个进程将自己的rank乘以10作为整数,并将一个实数数组的所有元素设置为当前rank(实数)。-进程0先发送两个消息(一个整数一个数组)给进程1,然后等待接收进程1发来的消息。-进程1则先等待接收进程0的消息,然后再发送自己的消息。-这样安排通信顺序避免了死锁:进程0先发送接收,进程1先接收发送-注意:我们使用了两个不同的消息标签(tag)来区分整数数组消息(send_tagsend_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_SendMPI_Recv中发生死锁?2.如何在Fortran中使用MPI_Sendrecv函数避免死锁?(如引用[3][4]所述)3.MPI_IsendMPI_Irecv非阻塞通信的使用方法?4.如何在多对多通信中使用MPI_SendMPI_Recv?</think> 下面详细介绍在Fortran中使用MPI_SendMPI_Recv进行消息传递的方法,结合您提供的最新引用信息。 ### Fortran中MPI_SendMPI_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*, '需要至少2个进程' 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*, 'Rank', rank, ': 已发送数据', send_buffer call MPI_Recv(recv_buffer, buffer_size, MPI_REAL, source, send_tag, MPI_COMM_WORLD, status, ierr) print*, 'Rank', rank, ': 接收数据', recv_buffer else call MPI_Recv(recv_buffer, buffer_size, MPI_REAL, source, send_tag, MPI_COMM_WORLD, status, ierr) print*, 'Rank', rank, ': 接收数据', recv_buffer call MPI_Send(send_buffer, buffer_size, MPI_REAL, dest, send_tag, MPI_COMM_WORLD, ierr) print*, 'Rank', rank, ': 已发送数据', 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、付费专栏及课程。

余额充值