MPI - 从其他函数向 运行 进程发送消息

MPI - sending messages to processes that run from other functions

我有一个等级为 0 (MASTER) 的进程,它在函数 (FUNCA) 中 运行 执行以下操作:

...

get_moves_list(node,&moves_list,&moves_len,maximizing);
//for each rank in SLAVES
//MPI_Send a move to a SLAVE

我希望从属进程从 MASTER 接收消息,但是从属进程是 运行 from/inside 不同的函数 (FUNCB)

void run_slave(rank) {
    int move;
    //MPI_Recv a move from MASTER
    //Do some stuff with that move
    //Then return to caller 
}

主要是这样的

int main(int argc,char **argv)
{
    int rank,size;
    MPI_Init(NULL,NULL);
    MPI_Comm_rank(MPI_COMM_WORLD,&rank);
    MPI_Comm_size(MPI_COMM_WORLD,&size);

    if (rank == MASTER) {
        ...

        //CALL FUNCA

        ...
    } else {
        run_slave(rank);
        MPI_Finalize();
    }
}

对于 MPI,sending/receiving 消息到进程 运行 在不同函数中是否可能发生这样的事情?

如果有帮助,我正在尝试并行化一个 minimax 函数 (FUNCA),但程序的结构必须如上所述使用。

当程序启动时,MASTER 进程启动游戏并调用 minimax 以获得最大化玩家的最佳移动。

我有 minimax 的串行版本,目前正在尝试使用 MPI 对其进行并行化,但到目前为止运气不佳。

是的,你可以做到。这是一个完整的玩具程序,可以演示此功能:

#include <iostream>
#include "mpi.h"
#include "unistd.h"
#define MASTER 0
int pid, pnum;

void func1(void)
{
    int bcastval = 1000;
    MPI_Bcast(&bcastval, 1, MPI_INT, 0, MPI_COMM_WORLD);
    std::cout << pid << " sent " << bcastval << std::endl;
}

void func2(void)
{
    int recv;
    MPI_Bcast(&recv, 1, MPI_INT, 0, MPI_COMM_WORLD);
    std::cout << pid << " received " << recv << std::endl;
}


int main(void)
{
    MPI_Init(NULL,NULL);
    MPI_Comm_rank(MPI_COMM_WORLD,&pid);
    MPI_Comm_size(MPI_COMM_WORLD,&pnum);
    if (pid == MASTER) func1();
    else func2();
    if (pid == MASTER) std::cout << "Done!" << std::endl;
    MPI_Finalize();
}

请注意 运行 mpirun -np 2 ./a.out 产生

0 sent 1000
1 received 1000
Done!

但是,如果在主进程和其他进程调用的两个函数中有复杂的逻辑,我强烈建议避免这种情况。用这个例子很容易说明:

#include <iostream>
#include "mpi.h"
#include "unistd.h"
#define MASTER 0
int pid, pnum;

void func1(void)
{
    int bcastval = 1000;
    MPI_Bcast(&bcastval, 1, MPI_INT, 0, MPI_COMM_WORLD);
    std::cout << pid << " sent " << bcastval << std::endl;
    MPI_Barrier(MPI_COMM_WORLD); // any blocking call
}

void func2(void)
{
    int recv;
    MPI_Bcast(&recv, 1, MPI_INT, 0, MPI_COMM_WORLD);
    std::cout << pid << " received " << recv << std::endl;
}    

int main(void)
{
    MPI_Init(NULL,NULL);
    MPI_Comm_rank(MPI_COMM_WORLD,&pid);
    MPI_Comm_size(MPI_COMM_WORLD,&pnum);
    if (pid == MASTER) func1();
    else func2();
    if (pid == MASTER) std::cout << "Done!" << std::endl;
    MPI_Finalize();
}

任何时候在分支中进行阻塞 MPI 调用,并且并非所有进程都遵循该分支,MPI 将永远停止程序,因为并非所有进程都可以 "check in" 调用。此外,如果这些函数中发生了很多事情,那么就很难进行故障排除,因为调试输出的行为可能会出乎意料。

关于此的轶事:我在处理具有 MPI 层的大型多物理场仿真代码。最近,发生了与上述完全相同的事情,它停止了所有 17 位开发人员的整个代码。每个开发人员都发现代码在不同的位置停止,有时是在外部的、依赖于 MPI 的库中。排查了很久。

我建议FUNCA returnsmaster进程需要广播的信息,然后在条件分支外广播。

明确一点,MPI是一个结构化的通信库,而不是一些深奥的并行编程语言扩展。它只是促进了称为 ranks 的实体之间的结构化数据交换。通常,等级是 运行 在同一台计算机上或与某种网络链接的不同计算机上的进程,但这些也可能是其他类型的通信实体。重要的是,当涉及到代码执行时,每个等级都是独立的,它不关心其他等级在程序中的位置。更重要的是,它不关心其他队伍是否 运行 正在执行相同的程序。事实上,虽然 MPI 的所有等级 运行 具有相同的代码是典型的,但所谓的 SPMD 或 Single Program Mulitple Data,你可以自由地为一组等级甚至每个等级编写一个单独的程序,这被称为MPMD 或 M 多个 P 程序 M 多个 Data . MPI 甚至促进了经典的客户端-服务器模式,并允许连接单独的 MPI 作业。 SPMD 更易于编程,因为您只需编写一个程序。

将 MPI 简单地视为您的代码和系统特定 API 之间的中介(中间件),它支持轻松的进程间通信并抽象出诸如定位其他通信伙伴的实际端点(例如,找出在 TCP/IP 上通信 运行 时的网络地址和端口号)。当您编写通过网络与 WEB 服务器通信的浏览器时,您不关心服务器执行什么代码。相反,服务器不关心你的浏览器执行什么代码。只要两者使用相同的协议,通信就可以进行。这同样适用于 MPI - 只要两个等级使用相同的 MPI 库,它们就可以通信。

要在 MPI 中成功通信,只需要两件事(在典型的点对点数据交换中):

  • sender: rank A 愿意发送数据给 rank B 所以它调用 MPI_Send(..., B, tag, MPI_COMM_SOMETHING);
  • receiver: rank B 愿意从 rank A 接收数据所以它调用 MPI_Recv(..., A, tag, MPI_COMM_SOMETHING, ...);

只要两个级别都指定相同的标签和通信器,并且发送和接收调用中的地址成对匹配(包括接收方指定源和标签通配符的能力),则无论在何处进行交换都会发生找到了实际的代码行。

以下是一个完全有效的 MPI 示例:

rank_0.c

#include <stdio.h>
#include <mpi.h>

int main(void)
{
   MPI_Init(NULL, NULL);

   int rank;
   MPI_Comm_rank(MPI_COMM_WORLD, &rank);

   int a;
   MPI_Recv(&a, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
   printf("Rank %d got %d from rank 1\n", rank, a);

   MPI_Finalize();
   return 0;
}

rank_1.c

#include <mpi.h>
#include <stdio.h>

int main(int argc, char **argv)
{
   MPI_Init(&argc, &argv);

   int rank;
   MPI_Comm_rank(MPI_COMM_WORLD, &rank);

   int a = 42;
   MPI_Send(&a, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);
   printf("Rank %d sent %d to rank 0\n", rank, a);

   MPI_Finalize();
   return 0;
}

编译并运行:

$ mpicc -o rank_0 rank_0.c
$ mpicc -o rank_1 rank_1.c
$ mpiexec -n 1 ./rank_0 : -n 1 ./rank_1
Rank 0 got 42 from rank 1
Rank 1 sent 42 to rank 0

如您所见,这是两个完全不同的程序,它们仍然愉快地 运行 在一个 MPI 作业中一起工作,并且能够交换消息。