MPI_Sendrecv在3个进程上发生死锁

Bla*_*laz 3 c parallel-processing mpi

试图做一个"晕/鬼"行交换,我被一个死锁困住了(在图片下方的代码片段中).要交换的"光晕"行表示为深灰色线(在图中)和as hp[0]hp[M-1](在代码中).

[无法发布图片; 没有足够的声誉.再次用文字表达:hp[0]并且hp[M-1]是"晕"行(即要交换的行),hp[1]hp[M-2](和中间的所有行)都要用.计算.

为什么这个片段(适用于2个进程)可以解决3个进程的死锁问题?

// in-between processes ("P1" and "P2" in the picture; 
// one of "P1" and "P2" is of course missing in the case of 3 processes)
if (p > 0 && p < P-1) 
{ 
    MPI_Sendrecv(hp[M-2], N, MPI_DOUBLE, p+1, 0, 
                 hp[0],   N, MPI_DOUBLE, p-1, 0, MPI_COMM_WORLD, &s);  
    MPI_Sendrecv(hp[1],   N, MPI_DOUBLE, p-1, 1, 
                 hp[M-1], N, MPI_DOUBLE, p+1, 1, MPI_COMM_WORLD, &s);  
}
// root process ("P0" in the picture)
else if (p == 0) 
{
    MPI_Sendrecv(hp[M-2], N, MPI_DOUBLE, p+1, 0, 
                 hp[M-1], N, MPI_DOUBLE, p+1, 1, MPI_COMM_WORLD, &s);  
}
// last process ("P3" in the picture)
else 
{
    MPI_Sendrecv(hp[1],   N, MPI_DOUBLE, p-1, 1, 
                 hp[0],   N, MPI_DOUBLE, p-1, 0, MPI_COMM_WORLD, &s); 
}
Run Code Online (Sandbox Code Playgroud)

平台:Windows XP时DeinoMPI GUI其中有一个按钮,"Show Messages""Interrupts the running job and prints the current state of the message queues"

好吧,这是一个"当前状态"的例子(当处于死锁状态时):

Rank 0 queues:
 Posted receive queue:
  rank=2, tag=1, context_id=1(Collective), count=0, dtype=MPI_BYTE
Rank 1 queues:
 Posted receive queue:
  rank=0, tag=0, context_id=MPI_COMM_WORLD, count=10, dtype=MPI_DOUBLE
 Received but unmatched queue:
  rank=2, tag=2, context_id=MPI_COMM_WORLD, length=80
  rank=2, tag=2, context_id=MPI_COMM_WORLD, length=80
  rank=0, tag=1, context_id=1(Collective), length=0
Rank 2 queues:
 Posted receive queue:
  rank=1, tag=1, context_id=MPI_COMM_WORLD, count=10, dtype=MPI_DOUBLE
Run Code Online (Sandbox Code Playgroud)

为什么有MPI_BYTEas数据类型和1(Collective)上下文?为什么Rank 0他的接收队列中有一个rank = 2?!

PS:请原谅我,如果我要求(并且缺少)明显的东西,但我已经阅读了太多的SO问题,唉,没有找到解决方案.这么多,我知道HPC三重奏的Jonathan Dursi,High Performance Mark和suszterpatt.

更新(完整循环)

循环没有太多,所以我可以完整地发布它:它有一些评论MPI_Barrier s因为我随机尝试哪种组合可行(谈论"黑匣子").因此,除了那些MPI_Barrier(和MPI_Sccaterv循环之前)之外,没有任何其他通信正在进行.对于测试的目的,我做return 0;之前MPI_Gatherv之后的循环(所以这应该没有影响僵局以及).

while (1)
{
    difference = 0.0;

    //MPI_Barrier(MPI_COMM_WORLD);

    // in-between processes ("P1" and "P2" in the picture; 
    // one of "P1" and "P2" is of course missing in the case of 3 processes)
    if (p > 0 && p < P-1) 
    { 
        MPI_Sendrecv(hp[M-2], N, MPI_DOUBLE, p+1, 0, 
                     hp[0],   N, MPI_DOUBLE, p-1, 0, MPI_COMM_WORLD, &s);  
        MPI_Sendrecv(hp[1],   N, MPI_DOUBLE, p-1, 1, 
                     hp[M-1], N, MPI_DOUBLE, p+1, 1, MPI_COMM_WORLD, &s);  
    }
    // root process ("P0" in the picture)
    else if (p == 0) 
    {
        MPI_Sendrecv(hp[M-2], N, MPI_DOUBLE, p+1, 0, 
                     hp[M-1], N, MPI_DOUBLE, p+1, 1, MPI_COMM_WORLD, &s);  
    }
    // last process ("P3" in the picture)
    else 
    {
        MPI_Sendrecv(hp[1],   N, MPI_DOUBLE, p-1, 1, 
                     hp[0],   N, MPI_DOUBLE, p-1, 0, MPI_COMM_WORLD, &s); 
    }
    //MPI_Barrier(MPI_COMM_WORLD);

    // calculate "hpNEW" for each inner point
    for (y = 1; y < M-1; ++y)
        for (x = 1; x < N-1; ++x)
        {
            hpNEW[y][x] = (hp[y][x-1] + hp[y][x+1] + hp[y-1][x] + hp[y+1][x]) / 4.0;
            if (fabs( hpNEW[y][x] - hp[y][x] ) > diff)
                difference = fabs(hpNEW[y][x] - hp[y][x]);
        }

    if (difference < EPSILON)
        break;

    // transfer "hpNEW"'s calculated inner points to "hp" for next iteration 
    for (y = 1; y < M-1; ++y)
        for (x = 1; x < N-1; ++x)
            hp[y][x] = hpNEW[y][x];
} // while END
Run Code Online (Sandbox Code Playgroud)

一个进程确实会先break退出循环...这会导致死锁(在我不知道的其他可能的事情中)吗?如果是这样,如何预防呢?

关于"怪异" tag的另一件事.我只是运行上面的循环,所有MPI_Barrier的注释掉了......并得到了这个"奇怪的"(有一个tag=4!)消息队列状态:

Rank 0 queues:
 Posted receive queue:
  rank=1, tag=4, context_id=1(Collective), count=30, dtype=MPI_DOUBLE
 Received but unmatched queue:
  rank=2, tag=1, context_id=1(Collective), length=0
Rank 1 queues:
 Posted receive queue:
  rank=0, tag=0, context_id=MPI_COMM_WORLD, count=10, dtype=MPI_DOUBLE
 Received but unmatched queue:
  rank=2, tag=1, context_id=MPI_COMM_WORLD, length=80
Rank 2 queues:
 Posted receive queue:
  rank=1, tag=1, context_id=1(Collective), count=0, dtype=MPI_BYTE
Run Code Online (Sandbox Code Playgroud)

Jon*_*rsi 5

还有其他人,我们只是最近活跃的那些......

这对于Windows上的DeinoMPI来说很有意思,我没有意识到它有很好的工具来实时查看正在发生的事情.

所以你肯定不会问一些明显的事情; 从表面上看,我发现您发布的代码没有任何问题.我个人觉得使用类似MPI_PROC_NULL简化代码逻辑的东西更清楚:

left = p-1;
if (left < 0) left = MPI_PROC_NULL;
right = p+1;
if (right >= P) right = MPI_PROC_NULL;

MPI_Sendrecv(hp[M-2], N, MPI_DOUBLE, right, 0, 
             hp[0],   N, MPI_DOUBLE, left , 0, MPI_COMM_WORLD, &s);  
MPI_Sendrecv(hp[1],   N, MPI_DOUBLE, left , 1, 
             hp[M-1], N, MPI_DOUBLE, right, 1, MPI_COMM_WORLD, &s);  
Run Code Online (Sandbox Code Playgroud)

让MPI库处理边缘情况,而不是进行显式测试if (p == 0)等; 但这是一个品味的问题,以及之后您将如何处理代码.

消息队列中的情况令人困惑,以至于我认为您发布的代码不会导致死锁,尽管它可能在哪里(比如)排名1最终表现出死锁 - 它可能是排名第1的位置被视为悬挂.

如果你看看发生了什么,等级1等待等级0的10倍,等级2等待等级1的10倍,所以它就像你的光环填充的向右发送阶段 - 1和2已发布他们各自收到该阶段 - 除了2的标签是错误的,它接收10个标签1的双打,这不应该发生(通过上面的代码).

最重要的是,等级0正在等待该集合完成(与之相关的零数据 - 可能是屏障?或MPI_Finalize或隐含同步的其他内容?)因此不会发送到1; 排名1已经有一条消息作为该集体的一部分,所以如果它完成它将立即清除,并将其作为该集体的一部分.它还有两个消息已经从排名2开始,标签2?所以这必须来自当前代码片段之外的另一个通信阶段.

只是猜测我在队列中看到的内容,我猜测代码就像:

loop { 
    communication as posted above;

    another phase of communication;

    synchronization (barrier?)
}
Run Code Online (Sandbox Code Playgroud)

这是第二阶段的沟通,有一个微妙的错误.

更新:

好的,所以在不同时间退出循环的进程肯定会导致锁定,因为进程开始等待永远不会来自其邻居的消息.但这很容易解决; 在你本地计算出最大的差异之后,你会发现处理器之间的最大差异MPI_Allreduce; 只有当hp和hpNEW之间的全局差异到处都低于EPSILON时才会继续.

// calculate "hpNEW" for each inner point locally
for (y = 1; y < M-1; ++y)
    for (x = 1; x < N-1; ++x)
    {
        hpNEW[y][x] = (hp[y][x-1] + hp[y][x+1] + hp[y-1][x] + hp[y+1][x]) / 4.0;
        if (fabs( hpNEW[y][x] - hp[y][x] ) > diff)
            diff = fabs(hpNEW[y][x] - hp[y][x]);
    }

// find the maximum of all the local differences

MPI_Allreduce (&diff, &globaldiff, 1, MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD);

if (globaldiff < EPSILON)
    break;
Run Code Online (Sandbox Code Playgroud)