mpi4py 中的客户端-服务器编程

在上一篇中我们介绍了 mpi4py 中的 Op 对象,下面我们将介绍 mpi4py 中的客户端-服务器编程。

MPI 允许在不拥有相同通信子对象的两个进程组之间建立通信联系。该功能可以适用于很多场合,如:

  • 应用程序分两个部分,各自独立启动,但在运行时需要建立通信联系。
  • 某些可视化工具需要连接到正在运行的进程上。
  • 并行的 server 程序可能需要与多个并行的 client 建立连接。

MPI 提供了若干通信联系 API,这些通信联系 API 通过在两组进程之间建立组间通信子的方式来实现上述要求的功能。

在不共享通信子的两个进程组之间建立联系的操作是一种非对称的集合操作,需要强制一组进程进入监听状态来接收另一组进程的连接请求。MPI 称监听请求的一组进程为并行服务进程(parallel server),尝试连接服务组的进程称作客户端(client)。

由于没有预先存在的连接通道,因此客户端和服务器之间需要就一些关键的同步点达成一致。这就需要再有一个第三方机制来支持。MPI 为此设置了名字服务接口(可与外部域名服务兼容),通过一个可发布的端口号(port_name)供 server 端和 client 端建立连接。端口号可以是 TCP/IP 端口,也可能是系统使用任何其它协议定义的底层通信端口。MPI 通过名字发布机制还可把端口号与服务(service_name)绑定在一起,以 格式发布。端口号、服务及其发布机制也是在灵活性和可移植性之间的一个折中。仅用端口号可保证最好的移植性,但由于需人工参与通知 client,因此使用不方便;使用名字发布机制进行端口号、服务绑定的方式在支持名字发布的版本中可取得最好的移植性;也可在应用程序里自定义端口发现机制,此种方法最灵活,但移植性最差。

服务器端

服务器端首先调用 MPI.Open_port 打开端口,然后调用 MPI.Intracomm.Accept 等待客户端连接。

MPI.Open_port(Info info=INFO_NULL)

打开一个端口,返回打开的端口名字。端口名字也可结合 info 参数传递给 MPI 环境,字符串大小可由 MPI.MAX_PORT_NAME 指定。如果传人的 info 对象不作关于打开端口的任何指示,则可设置其值为 MPI.INFO_NULL,表示使用默认的开端口机制。注意:打开的端口号在通过 MPI.Close_port 并经 MPI 环境释放之前不可重复使用。另外,MPI-2 为 TCP/IP 方式的通信连接预留了关键字 ip_port 和 ip_address,并且仅供该函数使用。

MPI.Close_port(port_name)

关闭一个打开的端口 port_name 并释放占用的资源。

MPI.Intracomm.Accept(self, port_name, Info info=INFO_NULL, int root=0)

与客户端建立通信连接,在当前通信子上为集合操作,返回能够与客户端进行通信的组间通信子对象。port_name 是一个打开的有效端口,info 用于传递控制连接的辅助信息,root 是当前通信子中的根进程。如果一个服务器需要同时接受多个客户端的连接,则必须在每个单独的线程中调用 MPI.Intracomm.Accept。服务器端也可以使用单独的线程来处理没有客户端连接的情况。

客户端

MPI.Intracomm.Connect(self, port_name, Info info=INFO_NULL, int root=0)

通过端口 port_name 连接到服务器端,在当前通信子对象上为集合操作,返回一个组间通信子对象,其远程组由运行 MPI.Intracomm.Accept 的服务器端进程组成。info 用于传递控制连接的辅助信息,root 是当前通信子中的根进程。

MPI.Comm.Disconnect(self)

等待所有通信动作完成,然后释放通信子对象,并将其设置为 MPI.COMM_NULL。该方法不可对 MPI.COMM_WORLD 和 MPI.COMM_SELF 调用,也不可在通信中间而需等所有通信动作都完成时才可使用。除了要等待通信完成外,其与 MPI.Comm.Free 的效果类似。在断开两个进程连接的同时,通常还要调用 MPI.Win.Free 和 MPI.File.Close 来删除两个进程组间的所有通信路径。

名字发布

MPI.Publish_name(service_name, port_name, info=INFO_NULL)

服务器端发布名字绑定。一个 port_name 可绑定多个 service_name,反之则不一定允许。如果支持这样做,则需通过 info 对象传递名字搜索时的决策信息。

MPI.Unpublish_name(service_name, port_name, info=INFO_NULL)

服务器端撤销名字绑定。必须在关闭端口之前撤销名字服务的绑定关系。

MPI.Lookup_name(service_name, info=INFO_NULL)

根据 service_name 获取与其绑定的 port_name

建立 MPI 通信的其它方法

MPI.Comm.Join(type cls, int fd)

在两组 MPI 进程间通过套接字建立通信连接,返回一个组间通信子对象。fd 是一个 SOCK_STREAM 类型的套接字文件描述符。套接字必须处于连接态,并且在调用 MPI.Comm.Join 时不活跃。套接字两端的进程都必须调用该方法,两端的进程都从调用返回时才可取得组间通信子对象。

例程

下面给出使用例程。

服务器端程序

# server.py

"""
Server side of the MPI client/server programming model.

Run this with 1 processes like:
$ mpiexec -n 1 python server.py
"""

import numpy as np
from mpi4py import MPI


comm = MPI.COMM_WORLD

service_name = 'compute'
# open a port
port_name = MPI.Open_port()
# bind the opened port to a service_name,
# client can connect to the port by looking-up this service_name
MPI.Publish_name(service_name, port_name)
# wait for client to connect
inter_comm = comm.Accept(port_name)

# receive message from client
recv_obj = inter_comm.recv(source=0, tag=0)
print 'Server receives %s from client.' % recv_obj
send_obj = eval(recv_obj)
# reply the result to the client
print 'Server sends %s to client.' % send_obj
inter_comm.send(send_obj, dest=0, tag=1)

# unpublish the service_name, close the port and disconnect
MPI.Unpublish_name(service_name, port_name)
MPI.Close_port(port_name)
inter_comm.Disconnect()

客户端程序

# client.py

"""
Client side of the MPI client/server programming model.

Run this with 1 processes like:
$ mpiexec -n 1 python client.py
"""

import numpy as np
from mpi4py import MPI


comm = MPI.COMM_WORLD

service_name = 'compute'
# get the opened port of the server by looking-up a service_name
port_name = MPI.Lookup_name(service_name)
# connect to the server
inter_comm = comm.Connect(port_name)

# send message to the server
send_obj = '1 + 2'
print 'Client sends %s to server.' % send_obj
inter_comm.send(send_obj, dest=0, tag=0)
# get results from the server
recv_obj = inter_comm.recv(source=0, tag=1)
print 'Client receives %s from server.' % recv_obj

# disconnect from the server
inter_comm.Disconnect()

需要先启动服务器端程序,然后在另一个终端中启动客户端程序。

运行结果如下:

服务器端结果:

$ mpiexec -report-pid - -n 1 python server.py
31310
Server receives 1 + 2 from client.
Server sends 3 to client.

客户端结果:

$ mpiexec -ompi-server pid:31310 -n 1 python client.py
Client sends 1 + 2 to server.
Client receives 3 from server.

注意:以上运行方式仅适用于 OpenMPI,对其它的 MPI 实现,请参考其相关文档。

以上介绍了 mpi4py 中的客户端-服务器编程方法,在下一篇中我们将介绍 mpi4py 中的 memory 对象及内存操作。

你可能感兴趣的:(mpi4py 中的客户端-服务器编程)