Вы находитесь на странице: 1из 5

ТЕХНОЛОГИЯ ПАРАЛЛЕЛЬНОГО ПРОГРАММИРОВАНИЯ MPI

Лекция 2. Передача и прием данных между отдельными процессами

Функции передачи сообщений с точки зрения кол-ва процессов, вовлеченных в


коммуникацию делятся на две группы: функции обмена, предназначенные для взаимодействия
только двух процессов, типа “точка-точка” и функции обмена, в котором участвуют все
процессы некоторого коммуникатора, - функции коллективного обмена данными.
Во операции обмена типа “точка-точка” участвуют два процесса. Один процесс является
отправителем, другой является получателем. Процесс-отправитель должен вызвать одну из
функций передачи данных и явно указать в ней номер процесса получателя, тег сообщения и
коммуникатор, в рамках которого происходит передача данных. Процесс-получатель в свою
очередь должен вызвать одну из функций приема сообщения с указанием того же самого
коммуникатора. Номер процесса-отправителя и тег сообщения при этом может явно не
указываться.
Все функции обмена “точка-точка” делятся на два класса: функции блокирующего и не
блокирующего приема-передачи данных. Функции обмена с блокировкой приостанавливают
выполнение программы до выполнения некоторого условия, функции не блокирующего обмена
данными возвращают управление в точку вызова программы сразу же после выполнения
процедуры инициализации коммуникации. Неаккуратное обращение с блокирующими
функциями может привести к возникновению блокировки программы, в то же время
неаккуратное использование не блокирующих функций может привести к повреждению
данных.

Функции передачи данных с блокировкой

int MPI_Send (void *buf, int count, MPI_Datatype datatype, int dest, int tag,
MPI_Comm comm);
Аргументы buf, count, datatype определяют формат и адрес начала множества (массива)
данных, предназначенных для передачи. Аргументы dest, tag, comm и неявно присутствующий
номер процесса-отправителя формируют так называемую открытку сообщения (message
envelope). Тег сообщения tag должен принимать значения в отрезке [0;MPI_TAG_UB].
В результате вызова функции MPI_Send осуществляет буферизацию сообщения – или в
системный буфер, или в буфер приема сообщения. При этом вызов функции MPI_Recv с
соответствующими параметрами не обязателен. Если сообщение имеет небольшую длину, то
оно имеет все шансы быт помещенным в буфер, в этом случае MPI_Send возвращает
управление в программу не дожидаясь вызова функции MPI_Recv. Если сообщение не
помещается в буфер, то MPI_Send блокирует процесс до тех пор пока не будет вызвана
соответствующая функция MPI_Recv и не начнется реальная передача данных. В любом случае
возврат из функции MPI_Send произойдет тогда, когда все данные сообщения будут
перемещены или переданы принимающему процессу. Таким образом блокировка гарантирует
корректности повторного использования буфера buf передачи данных. Выбор реализации этой
гарантии лежит на разработчике MPI.
Возврат из функции MPI_Send не означает ни того что сообщение получено процессом
dest, ни того что сообщение вообще покинуло процессор, на котором выполняется процесс

1
инициатор передачи. Предоставляется только гарантия безопасного изменения значений в
буфере передачи.
Процесс может посылать сообщение самому себе – это не запрещено, но может привести к
тупиковой ситуации. Кроме этого процесс может послать сообщение несуществующему
процессу, приняв значение dest=MPI_PROC_NULL – такая операция передачи завершается
немедленно, при этом никакой передачи данных естественно не производится.

Модификации MPI_Send:
 MPI_Ssend – передача данных с синхронизацией с процессом получателем. Возврат
из функции произойдет только тогда, когда прием сообщения будет
инициализирован процессом-получателем одной из функций приема данных.
Таким образом, возврат из функции говорит не только о возможности безопасного
буфера передачи но и том, что процесс-получатель достиг точки приема данных.
Использование этой модификации может замедлять выполнение программы, но
позволяет избежать наличия в системе большого кол-ва буферизованных
непринятых сообщений.
 MPI_Bsend – передача сообщения с буферизацией. Если произошел вызов этой
функции, а соответствующий ей вызов функции приема нет, то сообщение будет
помещено в специальный буфер, после чего управление будет возвращено в
программу. Если буфер переполнен, то будет возврат с ошибкой. Для использования
этой модификации функции MPI_Send пользователь должен организовать буфер с
помощью функции
int MPI_Buffer_attach (void *buf, int size);
MPI позволяет организовать одновременно только один буфер. Удалить буфер
можно вызовом функции
int MPI_Buffer_detach (void *buf, int *size);
Функция удаления буфера блокирует выполнение программы до тех пор пока все
сообщения из буфера не будут переданы адресатам.
Перед вызовом функции MPI_Buffer_attach необходимо выделить память
средствами языка привязки, причем размер буфера желательно определять через
вызов функции
int MPI_Pack_size (int count, MPI_Datatype type, MPI_Comm comm, int *size );
с учетом величины MPI_BSEND_OVERHEAD:
real_size=size+ MPI_BSEND_OVERHEAD).
 MPI_Rsend – передача сообщения по готовности. Используется в случае наличия
гарантии того, что процесс-получатель вызвал соответствующую функцию приема.
В противном случае вызов этой функции считается ошибочным и результат этой
операции не определен. Данная модификация как правило имеет меньшие
накладные расходы на организацию коммуникации по сравнению с базовой
модификацией MPI_Send.

Функция приема данных с блокировкой


2
int MPI_Recv (void *buf, int count, MPI_Datatype datatype, int source, int tag,
MPI_Comm comm, MPI_Status *status);
Функция MPI_Recv принимает не более count единиц данных типа datatype и укладывает
их в память последовательно, начиная с адреса buf. Кол-во единиц данных в сообщении может
быть меньше либо равно значению count, в противном случае произойдет переполнение буфера
с возвратом соответствующей ошибки. Возврат из функции MPI_Recv произойдет тогда, когда
сообщение будет окончательно принято и уложено в буфер buf. Посмотреть параметры
принятого сообщения можно через обращение к структур status: поле MPI_SOURCE – номер
процесса отправителя, MPI_TAG – тег сообщения, MPI_ERROR – код ошибки. Реальный размер
сообщения можно определить функцией
int MPI_Get_count (MPI_Status *status, MPI_Datatype datatype, int *count);
в которую передаются структура status полученного сообщения, тип данных и указатель на
переменную для вывода размера сообщения.
В качестве значений аргументов source и tag в функции MPI_Recv можно использовать
предопределенные значения MPI_ANY_SOURCE и MPI_ANY_TAG для приема любого
сообщения от произвольного процесса в коммуникаторе. Это позволяет более оптимальным
способом реализовать, например, схему алгоритма “master-slave”
В MPI реализована возможность предварительной оценки получаемого сообщения с
помощью функции
int MPI_Probe (int source, int tag, MPI_Comm comm, MPI_Status *status);
Функция MPI_Probe блокирует выполнение программы до тех пор, пока в рамках
коммуникатора comm не появится сообщение от процесса source c тегом tag. Последующий
вызов функции MPI_Recv с теми же параметрами непосредственно принимает это сообщение.
Таким образом использование MPI_Probe позволяет проанализировать полученное
сообщение и на его основе принять решение о необходимых параметрах приема сообщения,
например, размера буфера, функцией MPI_Recv.

Функции приема-передачи данных без блокировки


Все функции приема-передачи сообщений с блокировкой имеют свои не блокирующие
аналоги. Их отличие состоит в том, что данные функции инициируют начало операции обмена
данными и сразу же возвращают управлении в программу. Дальнейшие операции по
установлению коммуникации протекают на фоне выполнения других операций программы. Это
позволяет совмещать операции вычисления и операции обмена данными, что оптимизирует
параллельную программу.
Условным недостатком не блокирующих функции является отсутствие гарантии
безопасного использования буфера приема-передачи данных до завершения коммуникации. Для
получения гарантий завершения коммуникации необходимо использование специальных
функций.
int MPI_Isend (void *buf, int count, MPI_Datatype datatype, int dest, int tag,
MPI_Comm comm, MPI_Request *request);
int MPI_Irecv (void *buf, int count, MPI_Datatype datatype, int source, int tag,
MPI_Comm comm, MPI_Request *request);

3
Для управления не блокирующими операциями обмена данными предназначен объект
request. Этот объект используется для установления статуса операции обмена и ожидания ее
завершения. Доступны следующие функции управления:
1) int MPI_Wait (MPI_Request *request, MPI_Status *status);
Функция MPI_Wait блокирует выполнение программы до тех пор, пока не завершится
операция обмена данными, ассоциированная с объектом request. После завершения
операции объект request уничтожается, а указатель на него устанавливается в
MPI_REQUEST_NULL.
2) int MPI_Test (MPI_Request *request, int *flag, MPI_Status *status);
Функция проверяет состояние операции обмена, ассоциированной с объектом request.
Если операция завершена, то устанавливает флаг flag в значение TRUE и осуществляет
тот же комплекс действий, что и функция MPI_Wait. В противном случае
устанавливает флаг flag в значение FALSE и возвращает управление.
3) int MPI_Waitany (int count, MPI_Request *array_of_request, int *index,
MPI_Status *status);
Функция MPI_Waitany блокирует выполнение программы до тех пор, пока не
завершится одна из операций обмена данными, ассоциированная с объектом из
массива array_of_request. Возвращает индекс объекта с завершенной коммуникацией.
4) int MPI_Testany (int count, MPI_Request *array_of_request, int *index,
int *flag, MPI_Status *status);
Функция проверяет состояние всех операции обмена, ассоциированных с объектами
array_of_request. Если хотя бы одна операция завершена, то устанавливает флаг flag в
значение TRUE. В противном случае устанавливает флаг flag в значение FALSE и
возвращает управление.
5) int MPI_Waitall (int count, MPI_Request *array_of_request, MPI_Status *array_of_status);
Функция MPI_Waitall блокирует выполнение программы до тех пор, пока не
завершатся все операции обмена данными, ассоциированная с объектами из массива
array_of_request.
6) int MPI_Testall (int count, MPI_Request *array_of_request, int *index,
int *flag, MPI_Status *array_of_status);
Функция проверяет состояние всех операций обмена, ассоциированных с объектами
array_of_request. Если все операции завершены, то устанавливает флаг flag в значение
TRUE. В противном случае устанавливает флаг flag в значение FALSE и возвращает
управление.
7) int MPI_Waitsome (int incount, MPI_Request *array_of_request, int *outcount,
int *array_of_index, MPI_Status *array_of_status);
Функция MPI_Waitsome блокирует выполнение программы до тех пор, пока не
завершится хотя бы одна из операций обмена данными, ассоциированная с объектом из
массива array_of_request. Возвращает индексы объектов с завершенной коммуникацией
и их статусы.
8) int MPI_Testsome (int incount, MPI_Request *array_of_request, int *outcount,
int *array_of_index, MPI_Status *array_of_status);
Функция выполняет те же действия, что и MPI_Waitsome, только без блокировки,
выполняя только анализ текущего состояния коммуникаций.
Отложенные запросы на взаимодействие
4
Иногда в программе алгоритмически требуется выполнить серию коммуникаций с одними
и теми же параметрами. В этом случае для оптимизации и уменьшения накладных расходов на
организацию коммуникаций используется следующий механизм: формируется отложенный
запрос на взаимодействие, затем используется по мере необходимости.
Формирование запросов на коммуникацию осуществляется функциями:
int MPI_Send_init (void *buf, int count, MPI_Datatype datatype, int dest, int tag,
MPI_Comm comm., MPI_Request *request);
int MPI_Recv_init (void *buf, int count, MPI_Datatype datatype, int source, int tag,
MPI_Comm comm, MPI_Request *request);
С каждым запросом ассоциируется объект request. Доступно формирование запросов на
передачу данных для всех модификаций этой операции.
Старт операции, ассоциированной с объектом request, осуществляется вызовом
int MPI_Start (MPI_Request *request);
или
int MPI_Startall (int count, MPI_Request *array_of_request);
для всех запросов из массива array_of_request.
Коммуникации стартуют в не блокирующем режиме. Дальнейшее управление ими
осуществляется по всем правилам работы с операциями не блокирующего приема-передачи.
Вызов функций типа MPI_Wait() для коммуникаций такого типа не удаляет
ассоциированный с ними объект request, а только устанавливает значение request в
MPI_REQUEST_NULL. Для удаления этого объекта необходимо вызвать функцию
int MPI_Request_free (MPI_Request *request);

Тупиковые ситуации
1) Несоответствие параметров приема и передачи сообщения.
2) Несоблюдение порядка приема-передачи: если один процесс передает несколько
сообщений одному и тому же процессу, то желательно, чтобы принимались они в том
же порядке.
3)
Процесс 0 Процесс 1
MPI_Send 1 MPI_Send 0
MPI_Recv 0 MPI_Recv 1

Процесс 0 Процесс 1
MPI_Recv 0 MPI_Recv 1
MPI_Send 1 MPI_Send 0

Оценить