1.6.1 Взаимодействие задач в PVM
В системе PVM каждая задача, запущенная на некотором процессоре, идентифицируется целым числом, которое называется идентификатором задачи (TID) и по смыслу похоже на идентификатор процесса в операционной системе Unix. Система PVM автоматически поддерживает уникальность таких идентификаторов: копии одного исполняемого файла, запущенные параллельно на N процессорах PVM, создают N задач с разными TID.
По стандарту принятому в PVM для взаимодействия задач считается, что в пределах одной PVM любая задача может передавать сообщения любой другой задаче, причем, размеры и число таких сообщений в принципе не ограничены. Это предположение существенно упрощает реализацию PVM на конкретных вычислительных комплексах, т.к. при этом контроль переполнения буферных устройств и массивов остается в ведении операционных систем и с программиста снимается одна лишняя забота.
Для повышения эффективности межзадачного обмена информацией предусмотрено использование нескольких алгоритмов. В частности, можно использовать алгоритм блокированной передачи, при котором функция "Послать сообщение" возвращает значение (т.е. завершает работу) только после того как получена положительная или отрицательная квитанция от получателя сообщения. Такой алгоритм передачи с ожиданием уведомления о доставке предпочтителен в тех случаях, когда длинное сообщение передается несколькими порциями, а также при обмене командами, последовательность выполнения которых во времени дорлжна быть строго фиксированной.
При использовании неблокированных алгоритмов передачи и приема сообщений уменьшаются простои процессоров, вызванные ожиданием реакции "собеседника". Особенно большой эффект это дает на приемной стороне при неизвестном времени прихода сообщения. Можно организовать работу приемного процессора так, чтобы он в ожидании сообщения выполнял текущую работу, лишь время от времени опрашивая приемный буфер.
Существенным является то обстоятельство, что при передаче последовательности сообщение от одной задачи к другой порядок приема сообщение всегда совпадает с порядком их передачи. Более того, если до обращения к функции "принять сообщение" в приемный буфер принимающей задачи записано несколько сообщений, то функция "принять сообщение" возвратит ссылку на первое принятое сообщение.
Память для буферных массивов на передающей и приемной стороне выделяется динамически, следовательно, максимальный объем сообщений ограничивается только объемом доступной памяти. Если одна из задач, запущенных в PVM, не может получить требуемую память для общения с другими задачами, то она выдает пользователю соответствующее сообщение об ошибке ("cannot get memory"), но другие задачи об этом событии не извещаются и могут, например, продолжать посылать ей сообщения.
1.6.2 Управление задачами
Управление задачами в PVM осуществляется на основе некоторого набора функций, о которых мы поговорим в этом разделе. Существует два варианта (два стиля) написания параллельных задач для PVM. В первом варианте весь исполняемый на всех процессорах код пишется как одна большая задача. Соответственно на каждом процессоре запускается на исполнение один и тот же файл. Обычно в самом начале своей работы программы вызывает функцию
callpvmfmytid( tid )
возвращающую значение идентификатора задачи tid >= 0, которым может определяться выбор для выполнения той или иной части программы. Эта функция может вызываться более одного раза.
После того , как задача определила, что она главная, выполняется запуск остальных частей задачи на других процессорах кластера. Запуск выполняется с помощью функции:
callpvmfspawn( task, flag, where, ntask, tids, numt )
task - имя исполняемого файла
INTEGERflag - опции запуска
where - указывает место запуска
INTEGER ntask - число запускаемых копий программы
INTEGER tids - массив значений tid для запущенных задач
Эта функция запускает в PVM ntask копий исполняемого файла с именем "task" с одинаковыми аргументами командной строки в массиве argv и возвращает число запущенных задач numt а также последовательность идентификаторов для запущенных задач. Причем, если numt Второй вариант написания параллельной задачи заключается в том, что для каждого процессора пишутся свои собственные задачи, выполняющие различные действия, и создается маленькая программа, которая, используя функцию pvm_spawn запускает все остальные задачи.
Исполняемый файл для функции pvm_spawn() должен находиться в строго определенном каталоге. Под Unix задача ищется в каталогах $PVM_ROOT/bin/$PVM_ARCH/ и $HOME/pvm3/bin/$PVM_ARCH. Задавать имя каталога в параметре "task" недопустимо.
Но это не единственный способ. В другом варианте исполняемый файл ищется (и запускается) не только на том компьютере, на котором работает вызвавшая pvm_spawn() задача, но в зависимости от параметров flag и where, на любом входящем в состав PVM. Например, если flag==0, то PVM сам выбирает, на какой из машин запускать новые задачи (главное, чтобы приложение было скомпилировано на этих машинах); а если flag & PvmMppFront > 0, то местом запуска будет выбран самый быстрый компьютер.
Значением параметра flag задается набор опций для запускаемых задач. Каждой опции сответствует целое неотрицательное число, и значение flag равно сумме выбранных опций. Ниже перечисляются опции запуска задач.
В FORTRANe flag может быть суммой следующих величин:
PVMDEFAULT=0 - PVM может выбрать любую машину для старта задачи
PVMHOST=1 - параметр where определяет машину для запуска
PVMARCH=2 - параметр where определяет тип архитектуры
PVMDEBUG=4 - процесс старует под отладчиком
PVMTRACE=8 - процесс генерирует PVM trace data.
Параметр where описывает на каких компьютерах кластера может быть запущена задача. Параметр является простой строковой переменной, в которую записано имя списка компьютеров. Списки компьютеров находятся в конфигурационных файлах системы PVM и формируются на этаме ее установки. Например в случае, когда в вашем кластере кроме консольной машины присутствует еще два компьютера: один класса P166 и другой класса P4, вы можете определить их в системе под именами "oldcomp" и "supercomp". И в зависимости от тех или иных условий, запускать свои задачи на какой-либо их этих машин кластера.
И, наконец, еще две функции, относящиеся к управлению задачами.
call pvmfkill( tid, info )
call pvmfexit( info )
Первая из них завершает выполнение задачи с идентификатором tid, возвращая при ошибке код ошибки info < 0. Отметим, что задача не может таким образом завершить свое выполнение. Вторая функция завершает работу PVM, запущенной пользователем, но при этом сама задача продолжает выполняться уже как обычная локальная задача и завершает работу обычным образом.
1.6.3 Передача сообщений
Посылка сообщений в PVM предназначена для передачи данных между различными процессам и состоит из трех шагов. Во-первых, буфер данных перед посылкой должен быть проинициализирован с использованием функций pvm_initsend() или pvm_mkbuf(). Во-вторых, пересылаемые данные должны быть "упакованы" в этот буфер. Для упаковки используется некоторе количество комбинаций вызовов функции pvm_pk*(). В FORTRANе упаковка данных производится подпрограмой pvmfpack(). Третий шаг заключается в пересылке данных адресатам. Для этой цели в зависимости от списка адресатов используется вызов функции pvm_send(), в параметрах которой указывается конкретный процесс-приемник, или функции pvm_mcast(), используемой для всенаправленной передачи (то есть всем процессам сразу).
Сообщение принимается процессом-адресатом с помощью соответствующей функции, после чего происходит распаковка принятого блока, извлечение хранящихся в нем даных и заполнение ими соотвествующих локальных переменных или массивов. Процедура приема сообщений может быть сконфигурирована в нескольких вариантах:
для приема любых сообщений
для приема любых сообщений от определенного источника
для приема любых сообщений с определенным message tag
для приема любых сообщений с определенным message tag от определенного источника
Кроме того, существует функция для проверки факта доставки сообщения адресату. Буфер сообщения
callpvmfinitsend( encoding, bufid )
Если пользователь использует только один буфер сообщения (обычно так и делается), то единственная необходимая для работы с буфером функция - это pvm_initsend(). Эта функция вызывается непосредственно перед упаковкой новой порции пересылаемых данных в буфер сообщения. Функция pvm_initsend освобождает буфер и создает новый для упаковки в него данных. Схема кодировки упаковываемых в буфер данных указывается заданием переменной encoding. Возвращаемое в переменную bufid значение является идентификатором буфера. Переменная encoding может принимать следующие значения:
PvmDataDefault - XDR кодировка, используемая в PVM по умолчанию. Эта кодировка используется обычно в гетерогенных кластерах, когда PVM не может знать понимает ли принимающая сторона передаваемый формат данных. Например, когда данные передаются с Linux-машины на Windows-машину. В случае, когда в кластере используется только один тип операционной машины или когда пользователь уверен, что принимающая сторона поймет все правильно, следует использовать тип кодировки PvmDataRaw. PvmDataRaw - без кодировки. Даные передаются без каких либо изменений. Если принимающая сторона не сможет правильно прочитать этот формат, это вызовет возврат кода ошибки в процессе распаковки. PvmDataInPlace - данные остаются на месте, не перемещаясь в буфер посылки. Этот тип кодировки можно использовать для снижения накладных расходов, связанных с перемещением данных в буфер. В этом случае буфер содержит только длины и указатели на передаваемые данные. Когда вызвана pvm_send(), данные копируются непосредственно с того места, где они расположены. Использование этой кодировки накладывает одно ограничение. Передаваемые данные не должны быть изменены между моментом, когда началась их упаковка и моментом окончания передачи буфера сообщения адресату. Однако, при использовании данного типа упаковки, имеется одно заметное преимущество. Функция упаковки pvm_initsend может быть вызвана только один раз в начале прогарммы. Например в начале работы программы мы можем упаковать данные из области перекрытия (см. главу "Декомпозиция данных") и передавать их множество раз по мере необходимости.