Вперед: 4.3.3. Неблокирующие коммуникационные операции
Назад: 4.3. Коммуникационные операции типа точка-точка
К содержанию: Оглавление


4.3.2. Блокирующие коммуникационные операции

Синтаксис базовых коммуникационных функций MPI_Send и MPI_Recv был приведен в параграфе 4.2, поэтому здесь мы рассмотрим только семантику этих операций.

В стандартном режиме выполнение операции обмена включает три этапа:

Поскольку операция выполняется в асинхронном режиме, адресная часть принятого сообщения состоит из трех полей:

  1. коммуникатора (comm), поскольку каждый процесс может одновременно входить в несколько областей связи;
  2. номера отправителя в этой области связи (source);
  3. идентификатора сообщения (tag), который используется для взаимной привязки конкретной пары операций посылки и приема сообщений.

Параметр count (количество принимаемых элементов сообщения) в процедуре приема сообщения должен быть не меньше, чем длина принимаемого сообщения. При этом реально будет приниматься столько элементов, сколько находится в буфере. Такая реализация операции чтения связана с тем, что MPI допускает использование расширенных запросов:

Не допускается расширенных запросов для коммуникаторов. Расширенные запросы возможны только в операциях чтения. Интересно отметить, что таким же образом организованы операции обмена в PSE nCUBE2 [13]. В этом отражается фундаментальное свойство механизма передачи сообщений: асимметрия операций передачи и приема сообщений, связанная с тем, что инициатива в организации обмена принадлежит передающей стороне.

Таким образом, после чтения сообщения некоторые параметры могут оказаться неизвестными, а именно: число считанных элементов, идентификатор сообщения и адрес отправителя. Эту информацию можно получить с помощью параметра status. Переменные status должны быть явно объявлены в MPI-программе. В языке C status - это структура типа MPI_Status с тремя полями MPI_SOURCE, MPI_TAG, MPI_ERROR. В языке FORTRAN status - массив типа INTEGER размера MPI_STATUS_SIZE. Константы MPI_SOURCE, MPI_TAG и MPI_ERROR определяют индексы элементов. Назначение полей переменной status представлено в таблице 4.4.

Таблица 4.4. Назначение полей переменной status.
Поля status C FORTRAN
Процесс-отправитель status.MPI_SOURCE status(MPI_SOURCE)
Идентификатор сообщения status.MPI_TAG status(MPI_TAG)
Код ошибки status.MPI_ERROR status(MPI_ERROR)

Как видно из таблицы 4.4, количество считанных элементов в переменную status не заносится.

Для определения числа фактически полученных элементов сообщения необходимо использовать специальную функцию MPI_Get_count:

C:

int MPI_Get_count (MPI_Status *status, MPI_Datatype datatype, int *count)

Fortran:

MPI_GET_COUNT (STATUS, DATATYPE, COUNT, IERROR)
INTEGER STATUS (MPI_STATUS_SIZE), DATATYPE, COUNT, IERROR

IN status - атрибуты принятого сообщения;
IN datatype - тип элементов принятого сообщения;
OUT count - число полученных элементов.

Подпрограмма MPI_Get_count может быть вызвана либо после чтения сообщения (функциями MPI_Recv, MPI_Irecv), либо после опроса факта поступления сообщения (функциями MPI_Probe, MPI_Iprobe). Операция чтения безвозвратно уничтожает информацию в буфере приема. При этом попытка считать сообщение с параметром count меньше, чем число элементов в буфере, приводит к потере сообщения.

Определить параметры полученного сообщения без его чтения можно с помощью функции MPI_Probe.

C:

int MPI_Probe (int source, int tag, MPI_Comm comm, MPI_Status *status)

Fortran:

MPI_PROBE (SOURCE, TAG, COMM, STATUS, IERROR)
INTEGER SOURCE, TAG, COMM, STATUS(MPI_STATUS_SIZE), IERROR

IN source - номер процесса-отправителя;
IN tag - идентификатор сообщения;
IN comm - коммуникатор;
OUT status - атрибуты опрошенного сообщения.

Подпрограмма MPI_Probe выполняется с блокировкой, поэтому завершится она лишь тогда, когда сообщение с подходящим идентификатором и номером процесса-отправителя будет доступно для получения. Атрибуты этого сообщения возвращаются в переменной status. Следующий за MPI_Probe вызов MPI_Recv с теми же атрибутами сообщения (номером процесса-отправителя, идентификатором сообщения и коммуникатором) поместит в буфер приема именно то сообщение, наличие которого было опрошено подпрограммой MPI_Probe.

При использовании блокирующего режима передачи сообщений существует потенциальная опасность возникновения тупиковых ситуаций, в которых операции обмена даннымиа блокируют друг друга. Приведем пример некорректной программы, которая будет зависать при любых условиях.

CALL MPI_COMM_RANK(comm, rank, ierr)
IF (rank.EQ.0) THEN
   CALL MPI_RECV(recvbuf, count, MPI_REAL, 1, tag, comm, status, ierr)
   CALL MPI_SEND(sendbuf, count, MPI_REAL, 1, tag, comm, ierr)
ELSE IF (rank.EQ.1) THEN
   CALL MPI_RECV(recvbuf, count, MPI_REAL, 0, tag, comm, status, ierr)
   CALL MPI_SEND(sendbuf, count, MPI_REAL, 0, tag, comm, ierr)
END IF

В этом примере оба процесса (0-й и 1-й) входят в режим взаимного ожидания сообщения друг от друга. Такие тупиковые ситуации будут возникать всегда при образовании циклических цепочек блокирующих операций чтения.

Приведем вариант правильной программы.

CALL MPI_COMM_RANK(comm, rank, ierr)
IF (rank.EQ.0) THEN
   CALL MPI_SEND(sendbuf, count, MPI_REAL, 1, tag, comm, ierr)
   CALL MPI_RECV(recvbuf, count, MPI_REAL, 1, tag, comm, status, ierr)
ELSE IF (rank.EQ.1) THEN
   CALL MPI_RECV(recvbuf, count, MPI_REAL, 0, tag, comm, status, ierr)
   CALL MPI_SEND(sendbuf, count, MPI_REAL, 0, tag, comm, ierr)
END IF

Другие комбинации операций SEND/RECV могут работать или не работать в зависимости от реализации MPI (буферизованный обмен или нет).

В ситуациях, когда требуется выполнить взаимный обмен данными между процессами, безопаснее использовать совмещенную операцию MPI_Sendrecv.

C:

int MPI_Sendrecv(void *sendbuf, int sendcount, MPI_Datatype sendtype,
int dest, int sendtag, void *recvbuf, int recvcount,
MPI_Datatype recvtype, int source, MPI_Datatypeа recvtag,
MPI_Comm comm, MPI_Status *status)

Fortran:

MPI_SENDRECV(SENDBUF, SENDCOUNT, SENDTYPE, DEST, SENDTAG, RECVBUF,
RECVCOUNT, RECVTYPE, SOURCE, RECVTAG, COMM, STATUS, IERROR)
<type> SENDBUF(*), RECVBUF(*)
INTEGER SENDCOUNT, SENDTYPE, DEST, SENDTAG, RECVCOUNT, RECVTYPE,
SOURCE, RECV TAG, COMM, STATUS(MPI_STATUS_SIZE), IERROR
IN sendbuf - адрес начала расположения посылаемого сообщения;
IN sendcount - число посылаемых элементов;
IN sendtype - номер процесса-получателя;
IN dest - идентификатор посылаемого сообщения;
IN sendtag - идентификатор посылаемого сообщения;
IN recvbuf - адрес начала расположения принимаемого сообщения;
IN recvcount - максимальное число принимаемых элементов;
IN recvtype - тип элементов принимаемого сообщения;
IN source - номер процесса-отправителя;
IN recvtag - идентификатор принимаемого сообщения;
IN comm - коммуникатор области связи;
OUT status - атрибуты принятого сообщения.

Функция MPI_Sendrecv совмещает выполнение операций передачи и приема. Обе операции используют один и тот же коммуникатор, но идентификаторы сообщений могут различаться. Расположение в адресном пространстве процесса принимаемых и передаваемых данных не должно пересекаться. Пересылаемые данные могут быть различного типа и иметь разную длину.

В тех случаях, когда необходим обмен данными одного типа с замещением посылаемых данных на принимаемые, удобнее пользоваться функцией MPI_Sendrecv_replace.

С:
MPI_Sendrecv_replace(void* buf, int count, MPI_Datatype datatype,
int dest, int sendtag, int source, int recvtag,
MPI_Comm comm, MPI_Status *status)
FORTRAN:
MPI_SENDRECV_REPLACE(BUF, COUNT, DATATYPE, DEST,
SENDTAG, SOURCE, RECVTAG, COMM, STATUS, IERROR)
<type> BUF(*)
INTEGER COUNT, DATATYPE, DEST, SENDTAG, SOURCE, RECVTAG, COMM,
STATUS(MPI_STATUS_SIZE), IERROR
INOUT buf - адрес начала расположения посылаемого и принимаемого сообщения;
IN buf - число передаваемых элементов;
IN count - тип передаваемых элементов;
IN datatype - номер процесса-получателя;
IN sendtag - идентификатор посылаемого сообщения;
IN source - номер процесса-отправителя;
IN recvtag - идентификатор принимаемого сообщения;
IN comm - коммуникатор области связи;
OUT status - атрибуты принятого сообщения.

В данной операции посылаемые данные из массива buf замещаются принимаемыми данными.

В качестве адресатов source и dest в операциях пересылки данных можно использовать специальный адрес MPI_PROC_NULL. Коммуникационные операции с таким адресом ничего не делают. Применение этого адреса бывает удобным вместо использования логических конструкций для анализа условий посылать/читать сообщение или нет. Этот прием будет использован нами далее в одном из примеров, а именно, в программе решения уравнения Лапласа методом Якоби.

Приведем пример программы на использование стандартных коммуникационных операций.

Программа blocking.c

#include "mpi.h"
#include <stdio.h>

int main(argc,argv) 
int argc;
char *argv[]; {
int numtasks, rank, dest, source, rc, count, tag=1; 
char inmsg, outmsg='x';
MPI_Status Stat;

MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD, &numtasks);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);

if (rank == 0) {
 dest = 1;
 source = 1;
 MPI_Send(&outmsg, 1, MPI_CHAR, dest, tag, MPI_COMM_WORLD);
 MPI_Recv(&inmsg, 1, MPI_CHAR, source, tag, MPI_COMM_WORLD, &Stat);
 } 
else if (rank == 1) {
 dest = 0;
 source = 0;
 MPI_Recv(&inmsg, 1, MPI_CHAR, source, tag, MPI_COMM_WORLD, &Stat);
 MPI_Send(&outmsg, 1, MPI_CHAR, dest, tag, MPI_COMM_WORLD);
 }

 MPI_Get_count(&Stat, MPI_CHAR, &count);
 printf("Task %d: Received %d char(s) from task %d with tag %d \n",
    rank, count, Stat.MPI_SOURCE, Stat.MPI_TAG);

MPI_Finalize();
}

Программа blocking.f

program blocking
  include 'mpif.h'

  integer numtasks, rank, dest, source, count, tag, ierr
  integer stat(MPI_STATUS_SIZE)
  character inmsg, outmsg
  outmsg = 'x'
  tag = 1

  call MPI_INIT(ierr)
  call MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierr)
  call MPI_COMM_SIZE(MPI_COMM_WORLD, numtasks, ierr)

  if (rank .eq. 0) then
   dest = 1
   source = 1
  call MPI_SEND(outmsg, 1, MPI_CHARACTER, dest, tag, 
 &      MPI_COMM_WORLD, ierr)
   call MPI_RECV(inmsg, 1, MPI_CHARACTER, source, tag, 
 &      MPI_COMM_WORLD, stat, ierr)
  else if (rank .eq. 1) then
   dest = 0
   source = 0
   call MPI_RECV(inmsg, 1, MPI_CHARACTER, source, tag, 
 &    MPI_COMM_WORLD, stat, err)
   call MPI_SEND(outmsg, 1, MPI_CHARACTER, dest, tag, 
 &    MPI_COMM_WORLD, err)
  endif

  call MPI_GET_COUNT(stat, MPI_CHARACTER, count, ierr)
  print *, 'Task ',rank,': Received', count, 'char(s) from task',
 &     stat(MPI_SOURCE), 'with tag',stat(MPI_TAG)

  call MPI_FINALIZE(ierr)
  end


Вперед: 4.3.3. Неблокирующие коммуникационные операции
Назад: 4.3. Коммуникационные операции типа точка-точка
К содержанию: Оглавление