Н.В. Вдовикина, А.В. Казунин, И.В. Машечкин, А.Н. Терехин - Системное программное обеспечение - взаимодействие процессов (2002) (1114651), страница 20
Текст из файла (страница 20)
buf – указатель на буфер, в котором нужно разместить тело принимаемого сообщения
count – максимальное количество элементов заданного типа, которое может уместиться в буфере
datatype – тип элементов в теле получаемого сообщения
dest – уникальный номер ветви-отправителя. С помощью этого параметра процесс получатель может указать конкретного отправителя, от которого он желает получить сообщение, либо сообщить о том, что он хочет получить сообщение от любой ветви данного коммуникатора – в этом случае в качестве значения параметра указывается константа MPI_ANY_SOURCE
tag – тэг (бирка) сообщения. Ветвь-получатель может инициировать прием сообщения с конкретным значением тэга, либо указать в этом параметре константу MPI_ANY_TAG для получения сообщения с любым значением тэга.
comm – коммуникатор, описывающий коммуникационный контекст, в котором принимается сообщение
status – указатель на структуру типа MPI_Status, поля которой будут заполнены функцией MPI_Recv(). Используя эту структуру, ветвь-получатель может узнать дополнительную информацию о сообщении, такую как, например, его фактический размер, а также фактические значения тэга и отправителя в том случае, если использовались константы MPI_ANY_SOURCE и MPI_ANY_TAG.
Если в момент вызова MPI_Recv() нет ни одного сообщения, удовлетворяющего заданным критериям (т.е. посланного через заданный коммуникатор и имеющего нужного отправителя и тэг, в случае, если были заданы их конкретные значения), то выполнение ветви блокируется до момента поступления такого сообщения. В любом случае, управление возвращается процессу лишь тогда, когда сообщение будет получено, и его данные будут записаны в буфер buf и структуру status.
Отметим, что фактический размер принятого сообщения может быть меньше, чем указанная в параметре count максимальная емкость буфера. В этом случае сообщение будет успешно получено, его тело записано в буфер, а остаток буфера останется нетронутым. Для того, чтобы узнать фактический размер принятого сообщения, служит функция MPI_Get_count():
#include <mpi.h>
int MPI_Get_count(MPI_Status *status, MPI_Datatype datatype, int *count);
Этой функции передаются в качестве параметров указатель на структуру типа MPI_Status, которая была заполнена в момент вызова функции приема сообщения, а также тип данных, образующих тело принятого сообщения. Параметр count представляет собой указатель на переменную целого типа, в которую будет записано количество элементов типа datatype, образующих тело принятого сообщения.
Если же при приеме сообщения его размер окажется больше указанной максимальной емкости буфера, функция MPI_Recv() вернет соответствующую ошибку. Для того чтобы избежать такой ситуации, можно сначала вызвать функцию MPI_Probe(), которая позволяет получить информацию о сообщении, не принимая его:
#include <mpi.h>
int MPI_Probe(int source, int tag, MPI_Comm comm, MPI_Status *status);
Параметры этой функции аналогичны последним 4-м параметрам функции MPI_Recv(). Функция MPI_Probe(), как и MPI_Recv(), возвращает управление только при появлении сообщения, удовлетворяющего переданным параметрам, и заполняет информацией о сообщении структуру типа MPI_Status, указатель на которую передается в последнем параметре. Однако, в отличие от MPI_Recv(), вызов MPI_Probe() не осуществляет собственно прием сообщения. После возврата из MPI_Probe() сообщение остается во входящей очереди и может быть впоследствии получено одним из вызовов приема данных, в частности, MPI_Recv().
-
MPI: прием сообщения, размер которого неизвестен заранее.
В данном примере приведена схема работы с сообщением, размер которого заранее неизвестен. Ветвь с номером 0 посылает ветви с номером 1 сообщение, содержащее имя исполняемой программы (т.е. значение нулевого элемента в массиве аргументов командной строки). В ветви с номером 1 вызов MPI_Probe() позволяет получить доступ к описывающей сообщение структуре типа MPI_Status. Затем с помощью вызова MPI_Get_count() можно узнать размер буфера, который необходимо выделить для приема сообщения.
#include <mpi.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int size, rank;
MPI_Init(&argc, &argv); /* Инициализируем библиотеку */
MPI_Comm_size(MPI_COMM_WORLD, &size);
/* Узнаем количество задач в запущенном приложении... */
MPI_Comm_rank (MPI_COMM_WORLD, &rank);
/* ...и свой собственный номер: от 0 до (size-1) */
if ((size > 1) && (rank == 0)) {
/* задача с номером 0 отправляет сообщение*/
MPI_Send(argv[0], strlen(argv[0]), MPI_CHAR, 1, 1, MPI_COMM_WORLD);
printf("Sent to process 1: \"%s\"\n", argv[0]);
} else if ((size > 1) && (rank == 1)) {
/* задача с номером 1 получает сообщение*/
int count;
MPI_Status status;
char *buf;
MPI_Probe(0, 1, MPI_COMM_WORLD, &status);
MPI_Get_count(&status, MPI_CHAR, &count);
buf = (char *) malloc(count * sizeof(char));
MPI_Recv(buf, count, MPI_CHAR, 0, 1, MPI_COMM_WORLD, &status);
printf("Received from process 0: \"%s\"\n", buf);
}
/* Все задачи завершают выполнение */
MPI_Finalize();
return 0;
}
7.2.6Коммуникации «точка-точка». Неблокирующий режим.
Во многих приложениях для повышения производительности программы бывает выгодно совместить отправку или прием сообщений с основной вычислительной работой, что удается при использовании неблокирующего режим приема и отправки сообщений. В этом режиме вызов функций приема либо отправки сообщения инициирует начало операции приема/передачи и сразу после этого возвращает управление вызвавшей ветви, а сам процесс передачи данных происходит асинхронно.
Однако, для того, чтобы инициировавшая операцию приема/передачи ветвь имела возможность узнать о том, что операция завершена, необходимо каким-то образом идентифицировать каждую операцию обмена. Для этого в MPI существует механизм так называемых «квитанций» (requests), для описания которых служит тип данных MPI_Request. В момент вызова функции, инициирующей операцию приема/передачи, создается квитанция, соответствующая данной операции, и через параметр-указатель возвращается вызвавшей ветви. Впоследствии, используя эту квитанцию, ветвь может узнать о завершении ранее начатой операции.
Отсылка и прием сообщений в неблокирующем режиме.
Для отсылки и приема сообщений в неблокирующем режиме служат следующие базовые функции:
#include <mpi.h>
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);
Все их параметры, кроме последнего, аналогичны параметрам функций MPI_Send() и MPI_Recv(). Последним параметром служит указатель на переменную типа MPI_Request, которая после возврата из данного вызова будет содержать квитанцию для инициированной операции отсылки или приема данных.
Возврат из функции MPI_Isend() происходит сразу же, как только система инициирует операцию отсылки сообщения, и будет готова начать копировать данные из буфера, переданного в параметре buf. Важно понимать, что возврат из функции MPI_Isend() не означает, что этот буфер можно использовать по другому назначению! Вызвавший процесс не должен производить никаких операций над этим буфером до тех пор, пока не убедится в том, что операция отсылки данных завершена.
Возврат из функции MPI_Irecv() происходит сразу же, как только система инициирует операцию приема данных и будет готова начать принимать данные в буфер, предоставленный в параметре buf, вне зависимости от того, существует ли в этот момент сообщение, удовлетворяющее остальным переданным параметрам (source, tag, comm). При этом вызвавший процесс не может полагать, что в буфере buf находятся принятые данные до тех пор, пока не убедится, что операция приема завершена. Отметим также, что у функции MPI_Irecv() отсутствует параметр status, так как на момент возврата из этой функции информация о принимаемом сообщении, которая должна содержаться в status, может быть еще неизвестна.
В неблокирующем режиме, также как и в блокирующем, доступны различные варианты буферизации при отправке сообщения. Для явного задания режима буферизации, отличного от стандартного, служат следующие функции:
#include <mpi.h>
int MPI_Issend(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request);
int MPI_Ibsend(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request);
int MPI_Irsend(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request);
Отметим, что поскольку MPI_Ibsend() предполагает обязательную буферизацию сообщения, а MPI_rsend() используется лишь тогда, когда на принимающей стороне уже гарантированно инициирован прием сообщения, их использование лишь в некоторых случаях может дать сколь либо заметный выигрыш по сравнению с аналогичными блокирующими вариантами, так как разницу во времени их выполнения составляет лишь время, необходимое для копирования данных.
Работа с квитанциями.
Библиотека MPI предоставляет целое семейство функций, с помощью которых процесс, инициировавший одну или несколько асинхронных операций приема/передачи сообщений, может узнать о статусе их выполнения. Самыми простыми из этих функций являются MPI_Wait() и MPI_Test():
#include <mpi.h>
int MPI_Wait(MPI_Request *request, MPI_Status *status);
int MPI_Test(MPI_Request *request, int *flag, MPI_Status *status);
Функция MPI_Wait() вернет управление лишь тогда, когда операция приема/передачи сообщения, соответствующая квитанции, переданной в параметре request, будет завершена (для операции отсылки, инициированной функцией MPI_Ibsend() это означает, что данные были скопированы в специально отведенный буфер). При этом для операции приема сообщения в структуре, на которую указывает параметр status, возвращается информация о полученном сообщении, аналогично тому, как это делается в MPI_Recv(). После возврата из функции MPI_Wait() для операции отсылки сообщения процесс может повторно использовать буфер, содержавший тело отосланного сообщения, а для операции приема сообщения гарантируется, что после возврата из MPI_Wait() в буфере для приема находится тело принятого сообщения.
Для того, чтобы узнать, завершена ли та или иная операция приема/передачи сообщения, но избежать блокирования в том случае, если операция еще не завершилась, служит функция MPI_Test(). В параметре flag ей передается адрес целочисленной переменной, в которой будет возвращено ненулевое значение, если операция завершилась, и нулевое в противном случае. В случае, если квитанция соответствовала операции приема сообщения, и данная операция завершилась, MPI_Test() заполняет структуру, на которую указывает параметр status, информацией о принятом сообщении.
Для того, чтобы узнать статус сразу нескольких асинхронных операций приема/передачи сообщений, служит ряд функций работы с массивом квитанций:
#include <mpi.h>
int MPI_Waitany(int count, MPI_Request *array_of_requests, int *index, MPI_Status *status);
int MPI_Testany(int count, MPI_Request *array_of_requests, int *index, int *flag, MPI_Status *status);
int MPI_Waitsome(int count, MPI_Request *array_of_requests, int *outcount, int *array_of_indices, MPI_Status *array_of_statuses);
int MPI_Testsome(int count, MPI_Request *array_of_requests, int *outcount, int *array_of_indices, MPI_Status *array_of_statuses);
int MPI_Waitall(int count, MPI_Request *array_of_requests, MPI_Status *array_of_statuses);
int MPI_Testall(int count, MPI_Request *array_of_requests, int *flag, MPI_Status *array_of_statuses);
Все эти функции получают массив квитанций в параметре array_of_requests и его длину в параметре count. Функция MPI_Waitany() блокируется до тех пор, пока не завершится хотя бы одна из операций, описываемых переданными квитанциями. Она возвращает в параметре index индекс квитанции для завершившейся операции в массиве array_of_requests и, в случае, если завершилась операция приема сообщения, заполняет структуру, на которую указывает параметр status, информацией о полученном сообщении. MPI_Testany() не блокируется, а возвращает в переменной, на которую указывает параметр flag, ненулевое значение, если одна из операций завершилась. В этом случае она возвращает в параметрах index и status то же самое, что и MPI_Waitany().
Отметим, что если несколько операций из интересующего множества завершаются одновременно, то и MPI_Waitany(), и MPI_Testany() возвращают индекс и статус лишь одной из них, выбранной случайным образом. Более эффективным вариантом в таком случае является использование соответственно MPI_Waitsome() и MPI_Testsome(): их поведение аналогично MPI_Waitany() и MPI_Testany() за тем исключением, что в случае, если одновременно завершается несколько операций, они возвращают статус для всех завершившихся операций. При этом в параметре outcount возвращается количество завершившихся операций, в параметре array_of_indices – индексы квитанций завершившихся операций в исходном массиве квитанций, а массив array_of_statuses содержит структуры типа MPI_Status, описывающие принятые сообщения (значения в массиве array_of_statuses имеют смысл только для операций приема сообщения). Отметим, что у функции MPI_Testany() нет параметра flag – вместо этого, она возвращает 0 в параметре outcount, если ни одна из операций не завершилась.
Функция MPI_Waitall() блокируется до тех пор, пока не завершатся все операции, квитанции для которых были ей переданы, и заполняет информацию о принятых сообщениях для операций приема сообщения в элементах массива array_of_statuses (при этом i-й элемент массива array_of_statuses соответствует операции, квитанция для которой была передана в i-м элементе массива array_of_requests). Функция MPI_Testall() возвращает ненулевое значение в переменной, адрес которой указан в параметре flag, если завершились все операции квитанции для которых были ей переданы, и нулевое значение в противном случае. При этом, если все операции завершились, она заполняет элементы массива array_of_statuses аналогично тому, как это делает MPI_Waitall().
-
MPI: коммуникации «точка-точка». «Пинг-понг».
В этом примере рассматривается работа в неблокирующем режиме. Процессы посылают друг другу целое число, всякий раз увеличивая его на 1. Когда число достигнет некоего максимума, переданного в качестве параметра командной строки, все ветви завершаются. Для того, чтобы указать всем последующим ветвям на то, что пора завершаться, процесс, обнаруживший достижение максимума, обнуляет число.