3.2. Блокирующие коммуникационные операции
Синтаксис базовых коммуникационных функций MPI_Send и MPI_Recv был приведен в разделе 2, поэтому здесь мы рассмотрим только семантику этих операций.
В стандартном режиме выполнение операции обмена включает три этапа:
Поскольку операция выполняется в асинхронном режиме, адресная часть принятого сообщения состоит из трех полей:
Параметр count (количество принимаемых элементов сообщения) в процедуре приема сообщения должен быть не меньше, чем длина принимаемого сообщения. При этом реально будет приниматься столько элементов, сколько находится в буфере. Такая реализация операции чтения связана с тем, что MPI допускает использование расширенных запросов:
Не допускается расширенных запросов для коммуникаторов. Расширенные запросы возможны только в операциях чтения. Интересно отметить, что таким же образом организованы операции обмена в PSE nCUBE2 [1]. В этом отражается фундаментальное свойство механизма передачи сообщений: асимметрия операций передачи и приема сообщений, связанная с тем, что инициатива в организации обмена принадлежит передающей стороне.
Таким образом, после чтения сообщения некоторые параметры могут оказаться неизвестными, а именно: число считанных элементов, идентификатор сообщения и адрес отправителя. Эту информацию можно получить с помощью параметра 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 представлено в таблице 3.2.
Поля status | C | FORTRAN |
Процесс-отправитель | status.MPI_SOURCE | status(MPI_SOURCE) |
Идентификатор сообщения | status.MPI_TAG | status(MPI_TAG) |
Код ошибки | status.MPI_ERROR | status(MPI_ERROR) |
Как видно из таблицы 3.2, количество считанных элементов в переменную status не заносится.
Для определения числа фактически полученных элементов сообщения необходимо использовать специальную функцию MPI_Get_count:
IN | status | - атрибуты принятого сообщения; |
IN | datatype | - тип элементов принятого сообщения; |
OUT | count | - число полученных элементов. |
Подпрограмма MPI_Get_count может быть вызвана либо после чтения сообщения (функциями MPI_Recv, MPI_Irecv), либо после опроса факта поступления сообщения (функциями MPI_Probe, MPI_Iprobe). Операция чтения безвозвратно уничтожает информацию в буфере приема. При этом попытка считать сообщение с параметром count меньше, чем число элементов в буфере, приводит к потере сообщения.
Определить параметры полученного сообщения без его чтения можно с помощью функции MPI_Probe.
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.
IN | sendbuf | - | адрес начала расположения посылаемого сообщения; |
IN | sendcount | - | число посылаемых элементов; |
IN | sendtype | - | тип посылаемых элементов; |
IN | dest | - | номер процесса-получателя; |
IN | sendtag | - | идентификатор посылаемого сообщения; |
OUT | recvbuf | - | адрес начала расположения принимаемого сообщения; |
IN | recvcount | - | максимальное число принимаемых элементов; |
IN | recvtype | - | тип элементов принимаемого сообщения; |
IN | source | - | номер процесса-отправителя; |
IN | recvtag | - | идентификатор принимаемого сообщения; |
IN | comm | - | коммуникатор области связи; |
OUT | status | - | атрибуты принятого сообщения. |
Функция MPI_Sendrecv совмещает выполнение операций передачи и приема. Обе операции используют один и тот же коммуникатор, но идентификаторы сообщений могут различаться. Расположение в адресном пространстве процесса принимаемых и передаваемых данных не должно пересекаться. Пересылаемые данные могут быть различного типа и иметь разную длину.
В тех случаях, когда необходим обмен данными одного типа с замещением посылаемых данных на принимаемые, удобнее пользоваться функцией MPI_Sendrecv_replace.
INOUT | buf | - | адрес начала расположения посылаемого и принимаемого сообщения; |
IN | count | - | число передаваемых элементов; |
IN | datatype | - | тип передаваемых элементов; |
IN | dest | - | номер процесса-получателя; |
IN | sendtag | - | идентификатор посылаемого сообщения; |
IN | source | - | номер процесса-отправителя; |
IN | recvtag | - | идентификатор принимаемого сообщения; |
IN | comm | - | коммуникатор области связи; |
OUT | status | - | атрибуты принятого сообщения. |
В данной операции посылаемые данные из массива buf замещаются принимаемыми данными.
В качестве адресатов source и dest в операциях пересылки данных можно использовать специальный адрес MPI_PROC_NULL. Коммуникационные операции с таким адресом ничего не делают. Применение этого адреса бывает удобным вместо использования логических конструкций для анализа условий посылать/читать сообщение или нет. Этот прием будет использован нами далее в одном из примеров, а именно, в программе решения уравнения Лапласа методом Якоби.