C++ MPI中异步发送的安全保证

C++ MPI中异步发送的安全保证,c++,mpi,C++,Mpi,在我的应用程序中,我使用MPI以主从方式分发作业。将作业交给从属作业,然后收集结果。在该程序的多线程版本中,当所有处理器同时尝试发送阻塞时,可能会出现死锁,因为没有匹配的Recv。我提出了一个似乎有效的解决方案,但我希望得到保证,而不是再测试一万次 我的程序的安全性是肯定的,如果这个小代码被保证提供一个一致的实现。显然,这只适用于两个处理器,不适用于更多: #include <cassert> #include "mpi.h" int main() { MPI::Init()

在我的应用程序中,我使用MPI以主从方式分发作业。将作业交给从属作业,然后收集结果。在该程序的多线程版本中,当所有处理器同时尝试发送阻塞时,可能会出现死锁,因为没有匹配的Recv。我提出了一个似乎有效的解决方案,但我希望得到保证,而不是再测试一万次

我的程序的安全性是肯定的,如果这个小代码被保证提供一个一致的实现。显然,这只适用于两个处理器,不适用于更多:

#include <cassert>
#include "mpi.h"

int main()
{
   MPI::Init();
   int ns[] = {-1, -1};
   int rank = MPI::COMM_WORLD.Get_rank();
   ns[rank] = rank;
   MPI::Request request = MPI::COMM_WORLD.Isend(&rank, sizeof(int), MPI::BYTE, 1 - rank, 0);
   MPI::COMM_WORLD.Recv(&ns[1 - rank], sizeof(int), MPI::BYTE, 1 - rank, 0);
   request.Wait();
   assert( ns[0] == 0 );
   assert( ns[1] == 1 );
   MPI::Finalize();
}
所以我的问题是:在我调用Wait on Isend返回的请求之前,Isend与Recv的交错在MPI中是定义良好的安全方法吗


免责声明:这段代码不是设计为异常安全或特别漂亮的。它仅用于演示目的

此代码不保证有效。在调用相应的等待函数之前,MPI实现可以自由地不执行任何与Isend相关的操作

更好的选择是使Send和Recv函数都不阻塞。您将使用Isend和Irecv,而不是使用Wait,而是使用Waitall。Waitall函数接受一组MPI请求,并等待所有请求,同时对每个请求进行处理

因此,您的程序将如下所示:

#include <cassert>
#include "mpi.h"

int main()
{
   MPI::Init();
   int ns[] = {-1, -1};
   int rank = MPI::COMM_WORLD.Get_rank();
   MPI::Request requests[2];
   ns[rank] = rank;
   requests[0] = MPI::COMM_WORLD.Isend(&rank, sizeof(int), MPI::BYTE, 1 - rank, 0);
   requests[1] = MPI::COMM_WORLD.Irecv(&ns[1 - rank], sizeof(int), MPI::BYTE, 1 - rank, 0);
   MPI::Request::Waitall(2, requests)
   assert( ns[0] == 0 );
   assert( ns[1] == 1 );
   MPI::Finalize();
}

你的代码是完全安全的。这由MPI标准§3.7.4-非阻塞通信语义中定义的非阻塞操作语义保证:

进行完成接收的对MPI_WAIT的调用最终将终止,并在匹配的发送已启动时返回,除非该发送已被另一个接收满足。特别是,如果匹配的发送是非阻塞的,那么即使发送方没有执行任何调用来完成发送,接收也应该完成。类似地,如果已启动匹配的接收,则完成发送的对MPI_WAIT的调用最终将返回,除非该接收已被另一个发送满足,并且即使没有执行完成接收的调用

在该上下文中,阻塞操作相当于启动非阻塞操作,紧接着是等待

如果标准中的文字不够令人放心,那么在OpenMPI中实现MPI_SENDRECV的代码部分可能会有所帮助:

if (source != MPI_PROC_NULL) { /* post recv */
    rc = MCA_PML_CALL(irecv(recvbuf, recvcount, recvtype,
                            source, recvtag, comm, &req));
    OMPI_ERRHANDLER_CHECK(rc, comm, rc, FUNC_NAME);
}

if (dest != MPI_PROC_NULL) { /* send */
    rc = MCA_PML_CALL(send(sendbuf, sendcount, sendtype, dest,
                           sendtag, MCA_PML_BASE_SEND_STANDARD, comm));
    OMPI_ERRHANDLER_CHECK(rc, comm, rc, FUNC_NAME);
}

if (source != MPI_PROC_NULL) { /* wait for recv */
    rc = ompi_request_wait(&req, status);
} else {
    if (MPI_STATUS_IGNORE != status) {
        *status = ompi_request_empty.req_status;
    }
    rc = MPI_SUCCESS;
}
无论您使用Irecv/Send/Waitreceive还是Isend/Recv/Waitsend,这两者在遇到可能的死锁时都同样安全。当然,如果交错操作没有正确匹配,死锁可能会发生


<> P>唯一的一件事就是代码使用到C++ MPI绑定。这些在MPI-2.2中被弃用,在MPI-3.0中被删除。您应该改用C API。

有趣。但这对我来说似乎很奇怪:看起来可以放几个Isend/iRecv,然后在最后等待所有的Isend/iRecv。这真的安全吗?唯一能让它不安全的是数据。在完成等待之前,无法修改传递到发送函数的缓冲区。类似地,在完成等待之前,您无法读取传递给Recv的缓冲区。哦,天哪,似乎我使用的MPI方式太复杂了。非常感谢。你第一段的陈述是不正确的。MPI标准在§3.7.4中明确规定,即使与仍然未等待的非阻塞操作匹配,非阻塞操作或阻塞操作的等待也应分别完成,因此MPI_ISEND/MPI_RECV/MPI_WAITsend不应死锁。MPI_Irecv/MPI_Send/MPI_Wait正是OpenMPI实现MPI_Sendrecv调用的方式。如何保证在示例代码中调用Wait?这两个进程都在等待之前调用阻塞Recv。要么后台进程线程负责非阻塞操作的进程,要么在单线程实现中,阻塞接收也会进行非阻塞发送。