C MPI发送/接收程序从未完成

C MPI发送/接收程序从未完成,c,mpi,deadlock,C,Mpi,Deadlock,我只是花了一段时间写了一个很长的回答别人的问题,只是为了它被删除之前,我可以张贴的答案。我不想白费力气,所以我把问题和答案贴在这里 这不仅仅是一个关于发送/接收死锁的标准答案,因为我还发现了一个只在某些编译器上工作的有趣的半解决方案 在并行课程中,我们需要做一个基于主从设计模式的练习,其中主进程0向其所有从进程发送一条消息,该从进程将向其左右邻居重新发送该消息(处理器id+/-1,除了处理器0没有左邻居和最后一个处理器id没有右邻居之外)。将消息重新传递给邻居后,从处理器向主处理器发送作业结束确

我只是花了一段时间写了一个很长的回答别人的问题,只是为了它被删除之前,我可以张贴的答案。我不想白费力气,所以我把问题和答案贴在这里

这不仅仅是一个关于发送/接收死锁的标准答案,因为我还发现了一个只在某些编译器上工作的有趣的半解决方案

在并行课程中,我们需要做一个基于主从设计模式的练习,其中主进程0向其所有从进程发送一条消息,该从进程将向其左右邻居重新发送该消息(处理器id+/-1,除了处理器0没有左邻居和最后一个处理器id没有右邻居之外)。将消息重新传递给邻居后,从处理器向主处理器发送作业结束确认

这个练习很简单,但我的代码中有一个问题,因为我在程序开始时收到确认结束消息…我不明白这里有什么问题。我尝试了使用fflush,但实际上程序的最后一行应该在接收之后才写入控制台

有人知道吗?我对MPI/C概念还不熟悉,所以我的工作可能有问题

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mpi.h>

int main(int argc, char *argv[]){
    int np, myId;
    char send[100], recv[100];

    MPI_Init(&argc, &argv);

    MPI_Comm_size(MPI_COMM_WORLD, &np);
    MPI_Comm_rank(MPI_COMM_WORLD, &myId);

    MPI_Status stat;
    if(myId == 0){
        int t = sprintf(send, "hey!"); //MPI_get_processor_name
        for(int i = 1; i < np; i++){
            printf("send %d => %d\n", myId, i);
            fflush(stdout);
            MPI_Send(send, 50, MPI_CHAR, i, 0, MPI_COMM_WORLD);
        }

        for(int i = 1; i < np; i++){
            MPI_Recv(recv, 50, MPI_CHAR, i, 0, MPI_COMM_WORLD, &stat);
            printf("%s\n", recv);
            fflush(stdout);
        }


    }else{
        if(myId < (np - 1)){
            printf("send %d => %d\n", myId, myId + 1);
            fflush(stdout);
            MPI_Send(send, 50, MPI_CHAR, myId + 1, 0, MPI_COMM_WORLD);
        }

        if(myId > 1){
            printf("Envoie %d => %d\n", myId, myId - 1);
            fflush(stdout);
                    MPI_Send(send, 50, MPI_CHAR, myId - 1, 0, MPI_COMM_WORLD);
        }

        MPI_Recv(send, 50, MPI_CHAR, MPI_ANY_SOURCE, 0, MPI_COMM_WORLD, &stat); 

        printf("Réception %d <= %d\n", myId, 0);
        fflush(stdout);

        if(myId != (np - 1)){
            MPI_Recv(send, 50, MPI_CHAR, myId + 1, 0, MPI_COMM_WORLD, &stat);
            printf("Receive %d <= %d\n", myId, myId + 1);
            fflush(stdout);
        }

        if(myId != 1){
            MPI_Recv(send, 50, MPI_CHAR, myId - 1, 0, MPI_COMM_WORLD, &stat);
            printf("Receive %d <= %d\n", myId, myId - 1);
            fflush(stdout);
        }

        int t = sprintf(recv, "End for %d.", myId);
        MPI_Send(recv, 50 , MPI_CHAR, 0, 0, MPI_COMM_WORLD); 
    }

    MPI_Finalize();
    return 0;
}
#包括
#包括
#包括
#包括
int main(int argc,char*argv[]){
int-np,myId;
char send[100],recv[100];
MPI_Init(&argc,&argv);
MPI通信大小(MPI通信世界和np);
MPI通信等级(MPI通信世界和myId);
MPI_状态统计;
如果(myId==0){
int t=sprintf(发送,“嘿!”);//MPI\u获取\u处理器\u名称
对于(int i=1;i%d\n”,myId,i);
fflush(stdout);
MPI_发送(发送,50,MPI_字符,i,0,MPI_通信世界);
}
对于(int i=1;i%d\n”,myId,myId+1);
fflush(stdout);
MPI_发送(发送,50,MPI_字符,myId+1,0,MPI_通信世界);
}
如果(myId>1){
printf(“环境%d=>%d\n”,myId,myId-1);
fflush(stdout);
MPI_发送(发送,50,MPI_字符,myId-1,0,MPI_通信世界);
}
MPI_Recv(发送、50、MPI_字符、MPI_任意源、0、MPI_通信世界和统计);
printf(“Ré异常%d解决方案1
让我们比较一下所有非0“从”内核实际上在做什么,以及您所说的它们应该做什么

您希望他们做什么:

主进程0向其所有从进程发送一条消息,该从进程将向其左右邻居(处理器id+/-1,处理器0没有左邻居和最后一个处理器id没有右邻居除外)重新发送消息。将消息重新传递给邻居后,从处理器向主处理器发送作业结束的确认

代码大纲:

Send_To_Right_Neighbour();

Send_To_Left_Neighbour();

Receive_From_Master();

Receive_From_Right_Neighbour();

Receive_From_Left_Neighbour();

Send_To_Master();
看到区别了吗?在将消息重新发送给邻居之前,从机没有收到来自主机的消息。将代码更改为:

Receive_From_Master();

Send_To_Right_Neighbour();

Send_To_Left_Neighbour();

Receive_From_Right_Neighbour();

Receive_From_Left_Neighbour();

Send_To_Master();
将修复该问题,然后代码为我运行到完成

出什么事了
MPI\u Send
可以是一个阻塞函数——也就是说,在另一个进程调用了匹配的
MPI\u Recv
之前,对
MPI\u Send
的调用不会返回(尽管它不一定是阻塞函数)。您应该假设它在编写代码时总是阻塞的

现在,让我们想象一下,当运行>5个进程时,非0进程会做什么

  • 进程1发送到它的右邻居(进程2),并在那里等待,直到进程2调用MPI_Recv
  • 进程2发送到它的右邻居(进程3),并在那里等待,直到进程3调用MPI_Recv
  • 进程3发送到它的右邻居(进程4),并在那里等待,直到进程4调用MPI_Recv
  • 进程n-2发送到它的右邻居(进程n-1),并在那里等待,直到进程n-1调用MPI_Recv
  • 进程n-1没有右邻居,因此继续发送到它的左邻居,并在那里等待,直到进程n-2调用
    MPI\u Recv
  • 这永远不会发生,因为进程n-2正忙着等待进程n-1在尝试从n-1接收数据之前接收其数据。这是一个死锁,两个进程都不会移动

    为什么解决方案有效 我已经说过,上面的解决方案对我来说是可行的,但它并不完美。我所做的唯一更改是将接收从进程0移动到第一步——为什么这会影响死锁

    答案是,它根本不应该影响死锁。我猜想编译器已经足够聪明,能够意识到每个内核都在向相同的邻居发送和接收数据,并将单独的
    MPI\u Send
    MPI\u Recv
    调用组合到
    MPI\u Sendrecv
    c中alls。这将在同一步骤中发送和接收到邻居,从而消除死锁问题。以前,从0接收的调用介于发送和接收到同一邻居之间,因此编译器无法将其优化为单个操作

    但是我们不想依赖于有一个好的编译器——您的代码应该在任何符合标准的编译器上工作——因此我们应该自己手动修复死锁问题,而不是依赖于编译器的聪明

    解决方案2 首先,对课程中迄今为止可能涉及或未涉及的内容发表一些评论

    • 进程0正在向所有其他内核发送相同的信息。如果您知道
      MPI\u Bcast
      ,则应使用该信息,而不是所有这些发送和接收信息。Receive_From_Master(); // Make sure all info is sent from left to right Send_To_Right_Neighbour(); // Make sure any info is received from left to right Receive_From_Left_Neighbour(); // Now send all info from right to left Send_To_Left_Neighbour(); // Make sure any info is received Receive_From_Right_Neighbour(); Send_To_Master();