使用 MPI 在后台收听和回复

Listening and replying in background with MPI

我想实现一个场景,其中一个进程有一些工作,系统中的所有其他进程都向这个进程发出请求。当然,这些其他进程不知道哪个进程有工作,所以它们必须向每个其他进程发送消息并检查它们是否有工作。如果进程没有工作,他们会回复 -1。当进程从有作业要捐赠的进程那里得到回复时,它将从中获取作业并开始执行作业。但是当它很忙时,如果它从任何其他进程收到请求,它会回复 -1。我没能得到这个工作。到目前为止,我已经在以下方面实施了 MPI_Send 和 MPI_Recv 的解决方案:

 /* Master process has all the jobs to donate */
if(rank == MasterProcess)
   while(haveJobToDonate){
       /* listen to requests with MPI_ANY_SOURCE,MPI_ANY_TAG */
       MPI_Recv()
        /* when request received send a job to requesting process */
       MPI_Send()
   }
   /* if no job, then finish */

}else if(rank != MasterProcess){
     /* find a job */
     findJob()

}

findJob()方法如下:

findJob(){
   /*set target to 0 */
   while(JobNotFound){
      MPI_Send(target)
      MPI_Recv(fromTarget)
      /* if no job returned */
      if(req == -1)
         target += (target%NumberOfProcesses)
      else
         return req

   }

}

但是你可以猜到问题是,如果一个进程没有作业,它不会回复请求进程,并且该进程将阻塞并等待。还有一个问题是,如果一个进程得到了一份工作,它就会做它的工作,它不会听取请求。如果一个进程发送了一个请求,那么它将阻塞并等待。这将使程序阻塞。 MPI 中是否有任何功能可以帮助解决这种情况?

你的问题的一些约束,比如问题中解释的,对我来说有点太模糊了,无法在答案中完全具体。例如,既然主进程是第一个分配工作的进程,为什么它不继续记账,即使在分配了所有工作之后,至少表明哪个进程拥有什么?这样,至少您想要更多工作的自由工人知道要从哪个其他工人那里偷东西。

更好的是,我想你的工作最初是由 master 分批分发的(否则,一旦 master 分发完,任何空闲的 worker 都知道没有什么可做的了)。因此,您可以查看 the various schedule policies of OpenMP 以了解如何更改批处理以管理整体负载平衡。这个问题在 OpenMP 中是一个非常常见的问题,它已经在那里得到解决,例如 schedule(guided)。这是一种您可以在主进程中很容易地重新实现的解决方案,它避免了任何类型的工人与工人之间的通信,因此比工作窃取更简单。

现在,假设窃取工作确实是您 want/need 要做的事情,一种可能的解决方案是使用一些单方面的通信。每个工作人员都会在向世界公开的内存 window 中记录其拥有的任务列表。然后,每当一个进程想要从它那里窃取一些工作时,它会用 MPI_Win_lock() 锁定这个 window,然后用 原子地 窃取任务 MPI_Compare_and_swap(),最后用MPI_Win_unlock()释放window。 MPI_Compare_and_swap() 的使用方式对应于将被探测的远程工作人员列表中的任务索引与当前进程想要窃取的任务进行比较。如果两者匹配,那么这意味着该任务可以自由窃取,并且当前进程窃取它并通过任何已经完成的方式替换它,或者,例如,减去它的等级以指示任何其他进程来后来它现在拥有它...操作的原子方面非常重要,因为另一个工人可能会尝试在同一时间做同样的事情。如果没有原子探测和更新操作,来自不同进程的探测和更新操作可能会相互交错,从而导致错误的解决方案。

在这个阶段,取决于是否可以从当前工作人员刚刚获得的新任务中窃取一些工作,它必须自动更新自己的共享window 的任务列表与一组 MPI_Win_lock() + MPI_Win_{put,accumulate,fetch_and_op}() + MPI_Win_unlock() 调用。然后,它可以开始实际处理其新获得的任务。

有关如何原子地使用这些单向调用的简单示例,您可以查看 this answer

注意,这仅适用于 MPI 3.0。