Н.В. Вдовикина, И.В. Машечкин, А.Н. Терехин, А.Н. Томилин - Операционные системы - взаимодействие процессов (2008) (1114653), страница 29
Текст из файла (страница 29)
Таким образом, если программист имеет возможностьдоступа к системе с общей памятью и с распределенной памятью,ему придется создавать отдельную версии своей программы дляработы на каждой из этих систем, осваивая при этом различныемодели программирования.В то же время, хотелось бы иметь некоторый единыймеханизм взаимодействия, который был бы реализован, и притомэффективно, для большинства или хотя бы для многих конкретныхпараллельных систем. В таком случае для перенесения программы содной архитектуры на другую было бы достаточно простойперекомпиляции, а программист, освоивший данное средство,получил бы возможность создавать эффективные программы дляширокого класса параллельных архитектур.
Одним из таких широкораспространенных средств параллельного программированияявляется MPI.MPI представляет собой стандарт, описывающий некотороемножествофункцийдляобменасообщениямимеждупараллельными процессами. Существует множество реализаций MPIдля различных параллельных архитектур, как с распределенной, таки с общей памятью. Как правило, эти реализации оформлены в виденабора библиотечных функций, которые можно использовать припрограммировании на языках Фортран и Си.В модели программирования MPI приложение представляетсобой совокупность процессов или нитей (иначе называемыхветвями), общающихся друг с другом путем передачи сообщений.При этом для организации обмена не является важным, будут липроцессы исполняться на одном процессоре или вычислительномузле или на разных – механизм обмена данными в обоих случаяходинаков.
Во всем, что не касается передачи и приема сообщений,182ветви являются независимыми и изолированными друг от друга.Отметим, что ветви приложения могут обмениваться сообщениями всреде MPI только между собой, но не с процессами другихприложений, исполняющимися в той же вычислительной системе.Помимо функций для обмена сообщениями, MPI предоставляетвозможности для взаимной синхронизации процессов и для решенияряда других вспомогательных задач.Количество ветвей в данном приложении задается в моментего запуска, т.е. не существует возможности порождать ветвидинамически во время исполнения приложения34. Запуск MPIприложения осуществляется с помощью специальной программы(чаще всего она называется mpirun), которой обычно указываетсяколичество ветвей, которые необходимо породить, имяисполняемого файла, а также входные параметры приложения.При написании программы с использованием MPI ееисходный текст должен содержать код для всех ветвей сразу, однаково время исполнения у каждой ветви имеется возможностьопределить свой собственный порядковый номер и общееколичество ветвей и в зависимости от этого исполнять ту или инуючасть алгоритма (данный подход в чем-то аналогичениспользованию системного вызова fork())7.2.3 Функции общегопрограммыназначения.ОбщаяструктураВсе без исключения функции Си-библиотеки MPI возвращаютцелое значение, описывающее код завершения операции.Существует специальная константа MPI_SUCCESS, соответствующаяуспешному завершению функции.
В случае неудачного завершениявозвращаемое значение будет отлично от MPI_SUCCESS, и равноодному из кодов ошибок, определенных в MPI и описывающихразличные исключительные ситуации.Все типы данных, константы и функции, относящиеся к Сибиблиотеке MPI, описаны в заголовочном файле <mpi.h>.34Заметим, что в версии стандарта MPI-2 описывается возможность динамическогопорождения ветвей, однако на момент написания данного пособия нам неизвестно ни об однойреализации MPI, в которой поддерживалось бы динамическое порождение ветвей, поэтомудалее мы будем рассматривать только модель со статическим порождением ветвей в моментзапуска приложения.183Коммуникаторы и группыВажными понятиями MPI являются группы ветвей икоммуникаторы. Группа ветвей представляет собой упорядоченноемножество ветвей. Каждой ветви в группе назначается уникальныйпорядковый номер – целое число в диапазоне [0, N-1], где N –количество ветвей в группе.При запуске приложения все его порожденные ветви образуютначальную группу.
Библиотека MPI предоставляет функции,позволяющие создавать новые группы на основе существующихпутем их разбиения, объединения, исключения ветвей из групп и т.п.операций.С каждой группой ветвей MPI связан так называемый«коммуникационный контекст», или «коммуникационное поле»,задающее множество всех возможных участников операций обменаданными и уникальным образом описывающее каждого участника.Кроме того, коммуникационный контекст может использоваться дляхранения некоторых общих для всех ветвей данной группы данных.Для описания коммуникационного контекста в MPI служат объектыспециального типа – коммуникаторы. Коммуникатор, илиописатель коммуникационного контекста, присутствует в качествепараметра во всех вызовах MPI, связанных с обменом данными.Подчеркнем, что любая операция обмена данными может бытьосуществлена только внутри определенного коммуникационногоконтекста, поэтому, например, сообщение, отправленное с помощьюкакого-либо коммуникатора, может бытьполучено только спомощью этого же самого коммуникатора.Коммуникаторы MPI описываются специальным типомданных MPI_Comm.
При запуске приложения сразу послеинициализации среды выполнения MPI каждой ветви становятсядоступны два предопределенных коммуникатора, обозначаемыхконстантами MPI_COMM_WORLD и MPI_COMM_SELF. MPI_COMM_WORLDпредставляетсобойкоммуникатор,описывающийкоммуникационный контекст начальной группы ветвей, т.е. всехветвей, порожденных при запуске приложения. MPI_COMM_SELF – этокоммуникатор, включающий одну-единственную ветвь – текущую, исоответственно, для каждой ветви он будет иметь свое значение.Каждая ветвь приложения имеет возможность узнать общееколичество ветвей в своей группе и свой собственный уникальныйномер в ней. Для этого служат функции MPI_Comm_size() иMPI_Comm_rank():#include <mpi.h>184int MPI_Comm_size(MPI_Comm comm, int *size);int MPI_Comm_rank(MPI_Comm comm, int *rank);В первом параметре каждой из этих функций передаетсякоммуникатор,описывающийкоммуникационныйконтекстинтересующей группы ветвей, во втором – указатель нацелочисленную переменную, в которую будет записан результат:для функции MPI_Comm_size() – количество ветвей в группе; дляфункции MPI_Comm_rank() – уникальный номер текущей ветви вгруппе.Отметим, что в MPI предусмотрена также возможность обменасообщениями между ветвями, принадлежащими разным группам.Для этого существует возможность создать так называемый интеркоммуникатор, объединяющий коммуникационные контексты,заданные двумя различными коммуникаторами, каждый из которыхописывает коммуникационный контекст некоторой группы.
Послесоздания такого коммуникатора его можно использовать дляорганизации обмена данными между ветвями этих групп.Обрамляющие функции. Инициализация и завершениеСамым первым вызовом MPI в любой программе,использующей эту библиотеку, должен быть вызов функцииMPI_Init() – инициализация среды выполнения MPI.#include <mpi.h>int MPI_Init(int *argc, char ***argv);Этой функции передаются в качестве параметров указатели напараметры, полученные функцией main(), описывающие аргументыкомандной строки.
Смысл этого заключается в том, что при загрузкеMPI-приложения mpirun может добавлять к его аргументамкомандной строки некоторые дополнительные параметры,необходимые для инициализации среды выполнения MPI. В этомслучае вызов MPI_Init() также изменяет массив аргументовкомандной строки и их количество, удаляя эти дополнительныеаргументы.Еще раз отметим, что функция MPI_Init() должна бытьвызвана до любой другой функции MPI, кроме того, в любойпрограмме она должна вызываться не более одного раза.По завершении работы с MPI в каждой ветви необходимовызвать функцию закрытия библиотеки – MPI_Finalize():#include <mpi.h>int MPI_Finalize(void);185После вызова этой функции невозможен вызов ни однойфункции библиотеки MPI (в том числе и повторный вызовMPI_Init())Для аварийного завершения работы с библиотекой MPIслужит функция MPI_Abort().#include <mpi.h>int MPI_Abort(MPI_Comm comm, int errorcode);Эта функция немедленно завершает все ветви приложения,входящие в коммуникационный контекст, который описываеткоммуникатор comm.
Код завершения приложения передается впараметре errorcode. Отметим, что MPI_Abort() вызываетзавершения всех ветвей данного коммуникатора даже в том случае,если она была вызвана только в какой-либо одной ветви.Синхронизация: барьерыДля непосредственной синхронизации ветвей впредусмотрена одна специальная функция – MPI_Barrier().MPI#include <mpi.h>int MPI_Barrier(MPI_Comm comm);С помощью этой функции все ветви, входящие вопределенный коммуникационный контекст, могут осуществить такназываемую барьерную синхронизацию. Суть ее в следующем: впрограмме выделяется некоторая точка синхронизации, в которойкаждая из ветвей вызывает MPI_Barrier(). Эта функция блокируетвызвавшую ее ветвь до тех пор, пока все остальные ветви из данногокоммуникационного контекста также не вызовут MPI_Barrier().После этого все ветви одновременно разблокируются.
Такимобразом, возврат управления из MPI_Barrier() осуществляется вовсех ветвях одновременно.Барьерная синхронизация используется обычно в тех случаях,когда необходимо гарантировать завершение работы над некоторойподзадачей во всех ветвях перед переходом к выполнениюследующей подзадачи.Важно понимать, что функция MPI_Barrier(), как и другиеколлективные функции, которые мы подробно рассмотрим ниже,должна быть обязательно вызвана во всех ветвях, входящих вданный коммуникационный контекст. Из семантики функцииMPI_Barrier() следует, что если хотя бы в одной ветви иззаданного коммуникационного контекста MPI_Barrier() вызвана не186будет, это приведет к «зависанию» всех ветвей, вызвавшихMPI_Barrier().Пример 33. Использование барьерной синхронизации.В данном примере иллюстрируется общая схема построенияпрограммы с использованием библиотеки MPI, а такжеиспользование функций общего назначения и барьернойсинхронизации.На экран выводится общее количество ветвей в приложении,затем каждая ветвь выводит свой уникальный номер.