4.2 MPI的异步通信(非阻塞收发)

  • 同步通信:当一个消息发送操作完全完成(消息接收者已经收到了该消息)时,称为同步发送;同理,当一个消息接收操作完全完成(消息接收者已经收到了该消息)时,称为同步接收。上文中提到的​​MPI_Send​​和​​MPI_Recv​​就是同步收发函数,或者说是阻塞的收发函数
  • 异步通信:当一个消息发送操作部分完成(不管消息接收者是否已经收到消息,我就是立即返回)时,称为异步发送;同理,当一个消息接收操作部分完成(不管是否接收到了消息,我就是立即返回)时,称为异步接收。下文中即将提到的​​MPI_Isend​​和​​MPI_Irecv​​就是异步收发函数,或者说是非阻塞的收发函数。

函数名

描述

MPI_Isend(void *buff,
int count,
MPI_Datatype type,
int dest,
int tag,
int comm,
MPI_Request *request )

发送一个点对点的异步消息到通信组comm中的dest进程;

该函数被调用后会立即返回,并通过request输出参变量返回消息发送状态。

需要注意的是,在确认消息已经完全发送出去之前,不要改变消息缓存区buff中的内容。

MPI_Recv(void *buff,
int count,
MPI_Datatype type,
int source,
int tag,
int comm,
MPI_Request *request )

从通信组comm中的source进程接收一个点对点的异步消息到buff中;

该函数被调用后会立即返回,并通过request输出参变量返回消息发送状态。

需要注意的是,在确认消息已经完全接收之前,不要改变消息缓存区buff中的内容。

  • 当你开始了一个异步收发操作后,你暂时是不能使用缓存区中的数据,直至收发操作执行完毕!那我怎么知道收发有没有结束呢?请看下面两个函数:

函数

描述

MPI_Test(MPI_Request *request,
int *flag,
MPI_Status * status)

该函数是非阻塞的;

它根据输入参数request,返回异步收发操作的状态;

其输出参变量flag有以下几种取值:

0:代表异步收发操作还未完成;

非0:代表异步收发操作已经完成,且status包含了该消息的信息(status.MPI_TAG、status.MPI_SOURCE)

MPI_Wait(MPI_Request *request,
MPI_Status * status)

该函数阻塞等待请求request(与某异步收发操作关联)完成;返回值status同上。

  • 同步(阻塞)方法求解数组中最值程序
  • 代码示例:
#include "mpi.h"
#include <stdio.h>
#include <stdlib.h>
#define MAX 10
int num_procs;

double x[MAX] = {1,3,56,24,54,54,35,245,23,52}; // Input array

int main(int argc, char *argv[])
{
int i,start, stop;
int myid;
double my_min, others_min; // Minimum
double my_max, others_max; // Maximum
MPI_Status st;

MPI_Init(&argc,&argv); // Initialize

MPI_Comm_size(MPI_COMM_WORLD, &num_procs); // Get # processors
MPI_Comm_rank(MPI_COMM_WORLD, &myid); // Get my rank (id)

/* --------------------------------------
* Find the min. among my numbers
* -------------------------------------- */
int n = MAX/num_procs;

start = myid * n; //某并发进程子任务的数组起始位置

if ( myid != (num_procs-1) ) //最后一个进程不一定能分到n个元素
{
stop = start + n;
}
else
{
stop = MAX;
}

my_min = x[start]; //假定起始位置为最小值

for (i = start+1; i < stop; i = i + 1 )
{
if ( x[i] < my_min )
my_min = x[i];
}

if ( myid == 0 )
{
/* -------------------------------------
Get the min from others and compare
------------------------------------- */
for (i = 1; i < num_procs; i++)
{
MPI_Recv(&others_min, 1, MPI_DOUBLE, i, 0, MPI_COMM_WORLD, &st);
if ( others_min < my_min )
my_min = others_min;
}
printf("The min number:%f\n",my_min);
}
else
{
MPI_Send(&my_min, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);
}


/* --------------------------------------
Now find the max. among my numbers
-------------------------------------- */
my_max = x[start];

for (i = start+1; i < stop; i = i + 1 )
{
if ( x[i] > my_max )
my_max = x[i];
}

if ( myid == 0 )
{
/* -------------------------------------
Get the max from others and compare
------------------------------------- */
for (i = 1; i < num_procs; i++)
{
MPI_Recv(&others_max, 1, MPI_DOUBLE, i, 0, MPI_COMM_WORLD, &st);
if ( others_max > my_max )
my_max = others_max;
}
printf("The max number:%f\n",my_max);
}
else
{
MPI_Send(&my_max, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);
}

MPI_Finalize();
}
  • 本代码中,因为收发函数是阻塞(同步)的,所以进程计算完之后执行收发操作时,必须阻塞着等待收发完成才能进行下一步操作,这无疑浪费了宝贵的时间。最好的方法是在程序的最后才进行同步处理。而这就不得不依靠下文叙述的异步处理机制了。
  • 但使用异步通信时必须要确保每一次异步收发,都必须使用不同的缓存区,以防止收发操作还未完成时缓存区被污染。
  • 异步(非阻塞)方法求解数组中最值程序
  • 代码示例:
#include "mpi.h"
#include <stdio.h>
#include <stdlib.h>
#define MAX 10
int num_procs;

double x[MAX] = {1,3,56,24,54,54,35,245,23,52}; // Input array

int main(int argc, char *argv[])
{
int i,start, stop;
int myid;
double my_min; // Minimum
double others_min[100]; // Save minimum separately
double my_max; // Maximum
double others_max[100]; // Save maximum separately

MPI_Status st;
MPI_Request rq_min[100], rq_max[100]; // Status variables

MPI_Init(&argc,&argv); // Initialize
MPI_Comm_size(MPI_COMM_WORLD, &num_procs); // Get # processors
MPI_Comm_rank(MPI_COMM_WORLD, &myid); // Get my rank (id)

/* --------------------------------------
Find the min. among my numbers
-------------------------------------- */
int n = MAX/num_procs;

start = myid * n; //某并发进程子任务的数组起始位置

if ( myid != (num_procs-1) ) //最后一个进程不一定能分到n个元素
{
stop = start + n;
}
else
{
stop = MAX;
}

my_min = x[start]; //假定起始位置为最小值

for (i = start+1; i < stop; i = i + 1 )
{
if ( x[i] < my_min )
my_min = x[i];
}
if ( myid == 0 )
{
/* -------------------------------------
Get the min from others and compare
------------------------------------- */
for (i = 1; i < num_procs; i++)
{
MPI_Irecv(&others_min[i], 1, MPI_DOUBLE, i, 0, MPI_COMM_WORLD, &rq_min[i]);
}
}
else
{
MPI_Isend(&my_min, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD,&rq_min[0]);
}


/* --------------------------------------
Now find the max. among my numbers
-------------------------------------- */
my_max = x[start];

for (i = start+1; i < stop; i = i + 1 )
{
if ( x[i] > my_max )
my_max = x[i];
}

if ( myid == 0 )
{
/* -------------------------------------
Get the max from others and compare
------------------------------------- */
for (i = 1; i < num_procs; i++)
{
MPI_Irecv(&others_max[i], 1, MPI_DOUBLE, i, 0, MPI_COMM_WORLD, &rq_max[i]);
}
}
else
{
MPI_Isend(&my_max, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD,&rq_max[0]);
}
/* --------------------------------------
Now synchronize to compute results
-------------------------------------- */
if ( myid == 0 )
{
for ( i = 1; i < num_procs; i++)
{
MPI_Wait( &rq_min[i], &st );

if ( others_min[i] < my_min )
my_min = others_min[i];
}

for ( i = 1; i < num_procs; i++)
{
MPI_Wait( &rq_max[i], &st );

if ( others_max[i] > my_max )
my_max = others_max[i];
}

printf("min = %f\n",my_min);
printf("max = %f\n",my_max);
}
else
{ // The other processes must wait until their messages
// has been received before exiting !!!
MPI_Wait( &rq_min[0], &st );
MPI_Wait( &rq_max[0], &st );
}

MPI_Finalize();
}