我只是花了一些时间给别人的问题写了一个很长的答案,只是为了在我发布答案之前将其删除。不想浪费精力,所以我在这里发布问题和答案。
这不仅仅是关于发送/接收死锁的标准答案,因为我还发现了一个只适用于某些编译器的有趣的半解决方案
在并行课程中,我们需要根据主从设计模式进行练习,其中主进程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;
}
答案 0 :(得分:5)
让我们比较所有非0,&#34;奴隶&#34;核心实际上是按照你说的应该做的。
您希望他们做什么:
主进程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_Send
可以成为阻止函数 - 即对MPI_Send
的调用不会返回,直到另一个进程调用了匹配的MPI_Recv
(虽然它没有 成为阻止函数)。你应该假设在编写代码时它总是会阻塞。
现在让我们想象当您使用&gt; 5个流程运行时,非0流程会执行哪些操作。
MPI_Recv
。MPI_Recv
。MPI_Recv
。MPI_Recv
MPI_Recv
。这种情况永远不会发生,因为进程n-2忙于等待进程n-1在尝试从n-1接收之前接收其数据。这是一个僵局,这两个过程都不会让步。
我已经说上述解决方案对我有用 - 但它并不完美。我做的唯一改变是将接收从进程0移到第一步 - 为什么这会影响死锁?
答案是它根本不应该影响死锁。我的猜测是编译器已经足够聪明地意识到每个核心正在向相同的邻居发送和接收,并将单独的MPI_Send
和MPI_Recv
调用组合到左右邻居中{{1调用。这在同一步骤中向邻居发送和接收,消除了死锁问题。以前,从0接收的调用是在发送和接收到同一个邻居之间,因此编译器无法将其优化为单个操作。
但是我们不想依赖于拥有一个好的编译器 - 你的代码应该适用于任何符合标准的编译器 - 所以我们应该自己手动修复死锁问题而不是依赖编译器聪明。
首先,对你在课程中可能会或可能没有涵盖的事情发表一些评论
MPI_Sendrecv
,你应该使用它而不是所有这些发送和接收。MPI_Bcast
非常简单地完成此操作。那就是说,让我们谈谈避免僵局。因此,根本问题是我们必须确保一个进程调用MPI_Gather
进程,另一个进程可以同时调用匹配的MPI_Send
而无需等待发送进程到做其他事情。问题出现在每个核心试图同时发送。
因此,我们可以解决的一个方法是确定信息首先完全向一个方向移动。我从左到右选择了。在这种情况下,每个从核都必须:
MPI_Recv
现在发生的是:
从左到右发送时会发生同样的情况,除了现在,进程1没有左邻居要发送,所以可以直接进入从进程2接收。在任何一个都没有死锁情况下。