使用 MPI 通道通信
要在集群应用程序中使用 MPI 通道,您需要将其与 mcoclmpi 库(而不是 mcocltcp)链接。根据 MPI 标准,MPI 程序中的第一个 MPI 调用必须是 MPI_Init() 或 MPI_Init_thread(),最后一个调用必须是 MPI_Finalize()。此外,每个进程只能调用一次 MPI_Init() 或 MPI_Init_thread() 以及 MPI_Finalize()。
MPI_Init() 和 MPI_Init_thread() 的主要区别在于,MPI_Init_thread() 允许应用程序设置所需的线程支持级别。集群运行时需要 MPI_THREAD_MULTIPLE 级别,这使得运行时能够从不同线程调用 MPI 函数而无需额外的同步。
基于上述要求,在集群应用程序中初始化 MPI 有两种方式:显式初始化和隐式初始化。在显式初始化中,应用程序显式调用 MPI_Init_thread() 和 MPI_Finalize(),并向 mco_cluster_db_open() 传递一个称为通信器的不透明 MPI 对象。通信器定义了通信上下文,即它决定了哪些进程参与通信。
MPI 运行时会自动创建包含所有进程的通信器 MPI_COMM_WORLD。此外,MPI API 还允许创建派生通信器。通信器通过 mco_clnw_mpi_params_t 结构中的 communicator 字段传递给 mco_cluster_db_open() 函数。如果此字段未设置(或为 NULL),则默认使用 MPI_COMM_WORLD。
在隐式初始化中,MPI_Init_thread() 和 MPI_Finalize() 将由集群运行时自动调用,并且会使用 MPI_COMM_WORLD 通信器。这种方式对开发人员来说稍微简单一些,应用程序仍然可以与 mcoclmpi 链接,或者如果不使用 MPI 通道,则可以与 mcocltcp 链接,无需进行任何更改。
显式初始化允许用户更灵活地管理 MPI 的生命周期。例如,如果 MPI 用于与 SmartEDB 集群无关的自定义通信,您可以根据需要控制 MPI 的启动和结束。以下示例展示了如何进行显式的 MPI 初始化:
main (int argc, char * argv)
{
mco_cluster_params_t cl_params;
mco_db_params_t db_params;
mco_db_h db;
mco_device_t devs [N];
int provided;
MPI_Init_thread (& argc, & argv, MPI_THREAD_MULTIPLE, & provided);
mco_cluster_init ();
mco_cluster_params_init (& cl_params);
cl_params.nw.mpi.communicator = (void *)MPI_COMM_WORLD;
<initialize db_params and devs>
mco_cluster_db_open (dbName, cldb_get_dictionary (), dev, n_dev, &db_params,
&cl_params);
/* start listener threads */
sample_start_connected_task (& listener_task, ClusterListener,
dbName, 0);
mco_db_connect(dbName, &db);
...
mco_cluster_stop (db);
sample_join_task (& listener_task);
mco_db_disconnect (db);
mco_db_close (dbName);
MPI_Finalize ();
return 0;
}
这与使用 TCP 的普通集群应用程序非常相似,唯一的区别在于 MPI 调用以及设置通信器参数:
cl_params.nw.mpi.communicator = (void *)MPI_COMM_WORLD;
在使用 MPI 通道时,mco_cluster_params_t 结构中的某些字段并非强制要求:
uint2 n_nodes - 集群中的节点总数
MPI 提供了 MPI_Comm_size() 函数,该函数返回通信器中的进程数量。因此,此参数是可选的。如果指定了 n_nodes 并且其值与 MPI_Comm_size 的返回值不同,则会返回错误代码 MCO_E_NW_INVALID_PARAMETER。
uint2 node_id - 节点的 ID
MPI 提供了 MPI_Comm_rank() 函数,该函数返回通信器中的进程 ID。因此,此参数也是可选的。如果指定了 node_id 并且其值与 MPI_Comm_rank() 的返回值不同,则会返回错误代码 MCO_E_NW_INVALID_PARAMETER。
mco_cluster_node_params_t * nodes - 节点列表
对于 MPI,这个节点列表也是可选的。它仍然可以用于指定进程的名称(即 mco_cluster_node_params_t 结构中的 addr 字段)。
此外,目前 MPI 不使用 check_quorum_func 和 check_quorum_param 字段,因为 MPI 不支持进程的动态连接或断开。
编译和启动 MPI 应用程序
要编译显式调用 MPI 函数并链接到 mcoclmpi 库的应用程序,您需要使用包装器脚本 mpicc(用于 C 语言)和 mpicxx(用于 C++)。这些脚本是 MPI 包的一部分,它们会调用 gcc/g++ 或其他标准编译器,并自动处理正确的包含路径、库路径、宏定义和其他编译选项,以确保正确构建 MPI 应用程序。
对于基于 UNIX 的系统,在 makefile 中指定 PRJ_F_CLUSTER = MPI 将导致 header.mak 自动设置 CC = mpicc 和 CXX = mpicxx,并将应用程序与 mcoclmpi 库进行链接。
要启动 MPI 应用程序,请使用 mpirun 或 mpiexec 命令。这些命令也是 MPI 包的一部分,具体命令格式为:
mpirun <mpirun arguments> <program> <program arguments>
最重要的 mpirun
参数有:
-n <nprocs>
- 集群中的进程(节点)数量-machinefile <filename>
- 包含节点列表的文本文件名,每行一个节点 例如,要在节点 nodeA 和 nodeB 上运行应用程序mpi_test
,创建名为 nodes 的文件,内容如下:
nodeA
nodeB
然后执行该命令:
mpirun -n 2 -machinefile ./nodes ./mpi_test
如前所述,对于 MPI 通道,n_nodes 和 node_id 并不是必需的参数,因此 mpi_test 可以在没有命令行参数的情况下启动。
当使用 MPI 通道时,底层传输方式(如 TCP、Infiniband 或共享内存等)由 MPI 工具自动选择。通常,这是通过 mpirun 的命令行选项或环境变量来配置的,但具体取决于所使用的 MPI 实现。MPI 通常会智能地选择最适合的传输方式。例如,在单个物理主机上运行多个进程时,MPI 会选择 IPC(共享内存)进行通信;如果不同主机之间有 Infiniband 支持,MPI 将优先使用 IB 传输;否则,它将默认使用 TCP。
需要注意的是,MPI 使用的是静态进程模型,这意味着它不提供方便且标准的方法来处理节点故障或动态添加新节点。虽然某些 MPI 实现可能提供了网络级容错、迁移等功能,但这些功能并不在 MPI 标准范围内,因此具体实现和可用性高度依赖于所使用的 MPI 库。