分布式内存 MIMD 系统最常用的编程方法是 message passing (消息传递),或消息传递的某些变体。 MPI 是使用最广泛的标准。

在基本消息传递中,进程通过显式发送和接收消息来协调它们的活动。 显式发送和接收消息称为点对点通信。

MPI 的发送和接收调用以下列方式运行:

  • 首先,进程 A 决定要向进程 B 发送消息。
  • 然后,进程 A 将其所有必要数据打包到进程 B 的缓冲区中。
  • 进程A表示应该通过调用Send函数将数据发送给进程B。
  • 在进程 B 可以接收数据之前,它需要确认它想要接收数据。 进程 B 通过调用 Recv 函数来完成此操作。

这样,进程每次发送消息,必然有一个进程也表示要接收消息。即对 Send 和 Recv 的调用总是成对的。

p9X3ETK.png

进程如何知道将消息发送到哪里?

当一个 MPI 程序第一次启动时,进程的数量是固定的(有一种方法可以创建更多的进程,但我们暂时忽略它。)每个进程被分配一个从 0 开始的唯一整数。这个整数被称为 进程的等级以及在发送和接收消息时如何识别每个进程。

MPI 进程被安排在逻辑集合中,这些逻辑集合定义允许哪些进程发送和接收消息。 这种类型的集合称为 communicator (通信器)。 通信器可以按层级排列,但在 MPI 中很少使用,这里不再赘述。 MPI 程序启动时存在一个特殊的通信器,它包含了 MPI 程序中的所有进程。 这个通讯器叫做 MPI.COMM_WORLD。 在 mpi4py 中,通信器由 Comm 类表示。

为了让进程了解其他进程,MPI 在通信器上提供了两种方法。

其中第一个称为 Get_size(),它返回通信器中包含的进程总数(通信器的大小)。

其中第二个称为 Get_rank(),它返回调用进程在通信器中的排名。 请注意,Get_rank() 将为 MPI 程序中的每个进程返回不同的值。

术语

为简单起见,我们将“排名为 N 的进程”和“进程 N”称为同一进程。

以下代码获取 MPI.COMM_WORLD 通信器的大小,以及通信器内进程的等级。 我们将创建一个名为 mpi1.py 的文件并运行此代码以查看每个进程的 size 和 rank 值。

1
2
3
4
5
6
from mpi4py import MPI

comm = MPI.COMM_WORLD
size = comm.Get_size()
rank = comm.Get_rank()
print('size=%d, rank=%d' % (size, rank))

现在使用命令运行程序:

1
mpiexec -n 4 python mpi1.py

输出

1
2
3
4
size=4, rank=1
size=4, rank=0
size=4, rank=2
size=4, rank=3

您注意到程序打印值的顺序是什么?提示:尝试运行该程序几次,看看会发生什么。

mpiexec

当一个 MPI 程序运行时,每个进程都包含相同的代码。 然而,正如我们所见,只有一个区别:每个进程都被分配了不同的排名值。 这允许将每个进程的代码嵌入到一个程序文件中。

在下面的代码中,所有进程都以相同的两个数字 a 和 b 开头。 然而,虽然只有一个文件,但每个进程对数字执行不同的计算。 进程 0 打印数字的总和,进程 1 打印数字相乘的结果,进程 2 打印最大值。

创建一个名为 mpi2.py 的程序,其中包含以下代码。

1
2
3
4
5
6
7
8
9
10
11
from mpi4py import MPI
rank = MPI.COMM_WORLD.Get_rank()

a = 6.0
b = 3.0
if rank == 0:
print(a + b)
if rank == 1:
print(a * b)
if rank == 2:
print(max(a,b))

使用以下命令运行此程序:

1
mpiexec -n 3 python mpi2.py

输出

1
2
3
9.0
18.0
6.0

Point-to-point communication

如前所述,最简单的消息传递涉及两个进程:发送方和接收方。 让我们首先演示为两个进程设计的程序。 一个将抽取一个随机数,然后将其发送给另一个。 我们将使用例程 Send 和 Recv 来完成此操作。

创建一个名为 mpi3.py 的程序,其中包含以下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import numpy
from mpi4py import MPI
comm = MPI.COMM_WORLD
rank = comm.Get_rank()

randNum = numpy.zeros(1)

if rank == 1:
randNum = numpy.random.random_sample(1)
print("Process", rank, "drew the number", randNum[0])
comm.Send(randNum, dest=0)

if rank == 0:
print("Process", rank, "before receiving has the number", randNum[0])
comm.Recv(randNum, source=1)
print("Process", rank, "received the number", randNum[0])

(未完)