В моем приложении я использую MPI для распределения заданий в режиме "ведущий-ведомый". Работа подчиняется рабочему, а затем собираются результаты. В многопоточной версии этой программы существовал потенциальный тупик, когда все процессоры одновременно пытаются Send
(блокировать), потому что не было соответствующего Recv
. Я придумал решение, которое, похоже, работает, но я хотел бы получить гарантию (кроме того, проверив ее еще десять тысяч раз).
Безопасность моей программы является несомненной, если этот маленький код гарантированно - при условии соответствующей реализации. (очевидно, это работает только для двух процессоров и не предназначено для большего):
#include <cassert>
#include "mpi.h"
int main()
{
MPI::Init();
int ns[] = {-1, -1};
int rank = MPI::COMM_WORLD.Get_rank();
ns[rank] = rank;
MPI::Request request = MPI::COMM_WORLD.Isend(&rank, sizeof(int), MPI::BYTE, 1 - rank, 0);
MPI::COMM_WORLD.Recv(&ns[1 - rank], sizeof(int), MPI::BYTE, 1 - rank, 0);
request.Wait();
assert( ns[0] == 0 );
assert( ns[1] == 1 );
MPI::Finalize();
}
Так что мой вопрос: Является ли разделительный из Isend
с Recv
, пока я не позову Wait
на Request
возвращенного Isend
хорошо определенно безопасная вещь в MPI?
(Отказ от ответственности: этот фрагмент кода не предназначен для исключительной безопасности или особенно красив. Он предназначен только для демонстрационных целей)
Ваш код абсолютно безопасен. Это гарантируется семантикой неблокирующих операций, определенных в стандарте MPI §3.7.4. - Семантика неблокирующих коммуникаций:
Прогресс. Вызов
MPI_WAIT
который завершает прием, в конце концов прекратится и вернется, если начата соответствующая отправка, если только передача не будет выполнена другим приемом. В частности, если сопоставление отправления не блокируется, то прием должен завершиться, даже если вызов не отправлен отправителем для завершения отправки. Аналогично, вызовMPI_WAIT
который завершает отправку, в конце концов будет возвращен, еслиMPI_WAIT
совпадение будет запущено, если только прием не будет удовлетворен другой отправкой, и даже если для завершения приема не будет выполнен вызов.
Операция блокировки в этом контексте эквивалентна инициализации неблокирующей, сразу же после которой происходит ожидание.
Если слова стандарта недостаточно обнадеживают, этот раздел кода из реализации MPI_SENDRECV
в Open MPI может помочь:
if (source != MPI_PROC_NULL) { /* post recv */
rc = MCA_PML_CALL(irecv(recvbuf, recvcount, recvtype,
source, recvtag, comm, &req));
OMPI_ERRHANDLER_CHECK(rc, comm, rc, FUNC_NAME);
}
if (dest != MPI_PROC_NULL) { /* send */
rc = MCA_PML_CALL(send(sendbuf, sendcount, sendtype, dest,
sendtag, MCA_PML_BASE_SEND_STANDARD, comm));
OMPI_ERRHANDLER_CHECK(rc, comm, rc, FUNC_NAME);
}
if (source != MPI_PROC_NULL) { /* wait for recv */
rc = ompi_request_wait(&req, status);
} else {
if (MPI_STATUS_IGNORE != status) {
*status = ompi_request_empty.req_status;
}
rc = MPI_SUCCESS;
}
Не имеет значения, используете ли вы Irecv/Send/Wait(receive)
или Isend/Recv/Wait(send)
- оба одинаково безопасны, когда дело доходит до возможных взаимоблокировок. Разумеется, взаимоблокировки могут (и будут) возникать, если чередующаяся операция неправильно согласована.
Единственное, что приводит к несоответствию вашего кода, это тот факт, что он использует привязки MPI C++. Они были устаревшими в MPI-2.2 и удалены в MPI-3.0. Вместо этого вы должны использовать API C.
Этот код не гарантированно работает. Реализации MPI могут не делать ничего, связанные с вашим Isend
пока вы не назовете соответствующую функцию Wait
.
Лучший вариант - сделать неблокирующими функции Send
и Recv
. Вы должны использовать Isend
и Irecv
и вместо использования Wait
вы будете использовать Waitall
. Функция Waitall
принимает массив запросов MPI и ждет их всех (одновременно делая прогресс на каждом).
Таким образом, ваша программа будет выглядеть так:
#include <cassert>
#include "mpi.h"
int main()
{
MPI::Init();
int ns[] = {-1, -1};
int rank = MPI::COMM_WORLD.Get_rank();
MPI::Request requests[2];
ns[rank] = rank;
requests[0] = MPI::COMM_WORLD.Isend(&rank, sizeof(int), MPI::BYTE, 1 - rank, 0);
requests[1] = MPI::COMM_WORLD.Irecv(&ns[1 - rank], sizeof(int), MPI::BYTE, 1 - rank, 0);
MPI::Request::Waitall(2, requests)
assert( ns[0] == 0 );
assert( ns[1] == 1 );
MPI::Finalize();
}
Isend
несколько Isend
/ IRecv
а затем просто подожду их всех в конце. Это действительно безопасно?
Send
пока вы не закончили Wait
. Точно так же вы не можете читать из буфера, который вы передаете в Recv
пока вы не закончите Wait
.