【问题标题】:Safety guarantee for asynchronous sends in MPIMPI中异步发送的安全保证
【发布时间】:2014-02-27 20:11:51
【问题描述】:

在我的应用程序中,我使用 MPI 以主从方式分配作业。将作业分配给从站,然后收集结果。在该程序的多线程版本中,当所有处理器同时尝试Send(阻塞)时,可能会出现死锁,因为没有匹配的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();
}

所以我的问题是:IsendRecv 的交错直到我在 Isend 返回的 Request 上调用 Wait 是 MPI 中定义明确的安全事物吗?

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

【问题讨论】:

    标签: c++ mpi


    【解决方案1】:

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

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

    在该上下文中的阻塞操作等同于启动非阻塞操作,然后立即等待。

    如果标准的措辞不够令人放心,那么 Open MPI 中 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 / Wait(receive) 还是Isend / Recv / Wait(send) 都没有关系——当涉及到可能的死锁时,两者都同样安全。当然,如果交错操作没有正确匹配,则可能(并且将会)发生死锁。

    使您的代码不符合标准的唯一原因是它使用了 C++ MPI 绑定。这些在 MPI-2.2 中已弃用并在 MPI-3.0 中删除。您应该改用 C API。

    【讨论】:

    • 如何保证示例代码中调用了Wait?两个进程在 Wait 之前调用一个阻塞的 Recv。
    • 要么是后台进程线程负责非阻塞操作的进程,要么(在单线程实现中)阻塞接收也进程非阻塞发送。
    【解决方案2】:

    不保证此代码有效。在您调用相应的Wait 函数之前,MPI 实现可以不做任何与您的Isend 相关的事情。

    更好的选择是让你的SendRecv 函数都是非阻塞的。您将使用IsendIrecv,而不是使用Wait,而是使用WaitallWaitall 函数接受一组 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();
    }
    

    【讨论】:

    • 有趣。但这对我来说似乎很奇怪:这看起来可以放几个 Isend / IRecvs 然后在最后等待所有这些。这真的安全吗?
    • 唯一可以使它不安全的是数据。在完成Wait 之前,您无法修改传递给Send 函数的缓冲区。同样,在完成Wait 之前,您无法从传递给Recv 的缓冲区中读取数据。
    • 哦,天哪,好像我使用 MPI 的方式太复杂了。谢谢!
    • 您第一段中的陈述不正确。 MPI 标准明确明确(第 3.7.4 节)即使在与仍在运行的非阻塞操作匹配时,对非阻塞操作(或分别为阻塞操作)的等待也应该完成没有等待,因此MPI_ISEND / MPI_RECV / MPI_WAIT(send) 不应该死锁。而MPI_Irecv / MPI_Send / MPI_Wait 正是Open MPI 实现MPI_Sendrecv 调用的方式。
    猜你喜欢
    • 2014-04-06
    • 2013-01-03
    • 2019-09-22
    • 1970-01-01
    • 2021-03-24
    • 2015-10-06
    • 2016-01-18
    • 1970-01-01
    • 2021-09-29
    相关资源
    最近更新 更多