MPI的点到点通信学习告一段落,这里对点到点通信进行总结。

通信模式总结

理解各种通信过程的行为,关键是弄清楚各个模式对缓冲使用的方式。各个模式使用缓冲的特点总结:

  1. 标准MPI_Send实际利用了MPI环境提供的默认缓冲区。
  2. 缓冲通信MPI_Bsend相当于将MPI环境提供的buffer放在用户空间进行管理,相当于用户手动开辟空间作为通信的缓冲区。
  3. 就绪通信MPI_Rsend不需要缓冲区,但是发送动作默认接受早已就绪,发送不能提前等待,否则报错。
  4. 同步通信MPI_Ssend与就绪通信类似,同样不需要缓冲区,但是发送动作是可以等待的,接受没有就绪就继续等待,不会报错。

异步(非阻塞)通信的工作原理类似,只不过可以将其理解为MPI又单独启动了一个线程在后台进行真正的通信操作,通过MPI_WaitxxxMPI_Testxxx等来进行检测是否通信的线程完成通信。

关于预防死锁

造成死锁的原因有很多,最典型的就是缓冲区争夺导致的死锁,标准通信中使用MPI环境提供的默认缓冲区,当发送/接受的数据量大于缓冲区大小时,就需要每个进程等待对方进程启动接受动作然后直接将数据发送(不通过缓冲区),但是这个时候如果两个进程都在等待对方接受,就会造成死锁。

下面是一段标准通信的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include "mpi.h"
#include <stdio.h>
#include <stdlib.h>

//#define BUFSIZE 65530
#define BUFSIZE 5530

int main(int argc, char ** argv)
{
int myid, nproc, other;

int sb[BUFSIZE];
int rb[BUFSIZE];
MPI_Status status;

MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myid);
MPI_Comm_size(MPI_COMM_WORLD, &nproc);

// Fill send buffer.
for (int i = 0; i < BUFSIZE; ++i)
{
sb[i] = myid + i;
}

other = (myid == 0)?1:0;

fprintf(stderr, "process: %d of %d trying sending...\n", myid, nproc);
MPI_Send(sb, BUFSIZE, MPI_INT, other, 1, MPI_COMM_WORLD);
fprintf(stderr, "process: %d of %d trying receiving...\n", myid, nproc);
MPI_Recv(rb, BUFSIZE, MPI_INT, other, 1, MPI_COMM_WORLD, &status);

MPI_Finalize();
return 0;
}

当我发送的数据较小时,的执行结果:
1
2
3
4
5
6
[zjshao@master 2-4]$ mpicc -std=c99 deadlock.c -o deadlock.x
[zjshao@master 2-4]$ mpiexec -n 2 -host node01 deadlock.x
process: 1 of 2 trying sending...
process: 0 of 2 trying sending...
process: 1 of 2 trying receiving...
process: 0 of 2 trying receiving...

但是当我们增大发送数据的大小时,则会出现死锁,两个进程互相等待对方的接收,但是却一直卡在发送过程,造成死锁。
1
2
3
4
5
[zjshao@master 2-4]$ mpiexec -n 2 -host node01 deadlock.x
process: 0 of 2 trying sending...
process: 1 of 2 trying sending...
[mpiexec@master.cluster] Sending Ctrl-C to processes as requested
[mpiexec@master.cluster] Press Ctrl-C again to force abort

解决死锁的方法

  1. 确保一次发送的数据量小于环境所能提供的缓冲区的容量
  2. 利用MPI环境消息发送接手的顺序性约束,调整语句的执行顺序,例如让进程0先发送再接收,进程1先接收,再发送。
  3. 使用缓冲发送,这样发送就会立即返回,不会造成死锁。
  4. 使用非阻塞通信,也会立即返回。
  5. 利用组合发送接收,对组合的发送接收操作,可以理解MPI环境对通信的匹配能够按需进行,即阻塞到Send时则对方的Recv就会前来匹配,反之亦然。

阻塞与非阻塞、同步与异步

阻塞操作

  • 阻塞的发送操作,其返回就意味着发送缓冲区可以被再次使用,而不会影响接收端的接收结果,但这并不意味着接收端已经完成了接收(因为数据可能仍保存在系统缓冲区内)
  • 阻塞的发送操作可以同步方式工作,此时发送和接收进程之间需要实施一个握手协议来确保发送动作的安全。
  • 阻塞的发送操作也可以异步方式进行,即缓冲通信,需要系统的缓冲区来进行消息缓存。
  • 阻塞的接收操作仅当消息接收完成才返回。

非阻塞操作

  • 非阻塞仅对MPI环境提出一个要求==在可能的时候启动通信。用户并没有办法预测这个启动具体发生在什么时候。
  • 在通过某种手段确定MPI环境确实执行了通信之前,修改发送缓冲区中的数据都是不安全的,即可能导致接收方接收到修改过的数据。
  • 非阻塞通信的主要目的之一是把计算和通信重叠起来,从而改进并行效率。

Comments