Параллельное программирование на языке Component Pascal

advertisement
Параллельное программирование на языке
Component Pascal – подсистема времени
выполнения Active BlackBox.
И. Е. Ермаков,
ООО ОЦПИ «Метасистемы»,
ГОУ ВПО «Орловский государственный университет»,
ermakov@metasystems.ru
В статье выполнен краткий обзор высокоуровневых абстракций параллельного программирования (ПП) в императивных языках, обзор
языка ПП Active Oberon и приведено описание разработанной автором
подсистемы времени выполнения Active BlackBox, позволяющей разрабатывать многопоточные приложения на языке Component Pascal
(Oberon-2) с использованием абстракций - активных процедур и активных объектов, с поддержкой динамической загрузки двоичных модулей
и сборки мусора. Active BlackBox представляет собой альтернативную
версию ядра для среды разработки BlackBox Component Builder.
Абстракции параллельного программирования
В связи с бурным развитием параллельных процессорных архитектур задачи ПП становятся сегодня все более актуальными и все более
сложными. Параллельные вычисления могут производиться на двух
уровнях - многопоточно на одной машине и распределено в компьютерной сети. Нами рассматривается только первый вид ПП - многопоточное программирование (МП). Выполнение многопоточного приложения
опирается на механизмы ОС. Все распространенные ОС (POSIXсемейство, Windows и т.д.) предоставляют сходные примитивы для МП:
функции порождения нового потока; приостановки, возобновления,
прерывания потока; набор примитивов для взаимного исключения и
синхронизации - критические секции, мьютексы, семафоры и т.п.
С ростом сложности задач стала очевидной невозможность построения крупных и надежных систем в терминах таких примитивов. Для
развития ПП потребовалось введение высокоуровневых абстракций,
которые были бы удалены от деталей аппаратуры и операционной системы, но приближены к предметной области. Даже сегодня большинство стандартных библиотек сред программирования таких абстракций
не предоставляют, являясь всего лишь оболочками над ОС. От оборачивания системных процедур в классы такие библиотеки не становятся
объектно-ориентированными, поскольку не позволяют разрабатывать
приложения в терминах предметной области.
- 386 -
В развитии высокоуровневых средств ПП можно выделить два
направления: первый - введение высокоуровневых конструкций ПП в
императивные языки (ИЯ), и второй - механизмы автоматического распараллеливания программ, написанных на функциональных языках
(ФЯ). Второй подход перспективен для прикладного программирования. В чистых ФЯ отсутствуют понятия состояния программы и побочного эффекта функции, что позволяет легко разделять выполнение на
произвольное число потоков. Однако есть ряд областей, для которых
естественным является написание программ на ИЯ, а не ФЯ. В частности, это системное программирование, включая программирование систем управления, встроенных и систем реального времени. Поэтому
развитие технологий императивного ПП представляет собой актуальную задачу.
В 1974 г. Хоар описал монитор - конструкцию, обеспечивающую
инкапсуляцию разделяемых данных, взаимное исключение и синхронизацию для взаимодействующих потоков. Монитор – это модуль, внутри
которого описаны разделяемые данные, доступные только через процедуры монитора. Процедуры монитора выполняются в режиме взаимного
исключения. Синхронизация внутри процедур монитора выполняется с
помощью т.н. условных переменных. К условным переменным применимы две операции: WAIT - стать в очередь ожидания на условную переменную, освободив монитор для входа другим потокам, и SIGNAL освободить первый поток из очереди. Мониторы были реализованы во
многих языках Pascal-семейства: Concurrent Pascal, Modula, а также в
значительно развитом виде активных объектов - в Active Oberon. Мониторы - пассивные сущности, реализующие взаимодействие потоков, но
не сами потоки. Первым языком, включившим в себя мощные высокоуровневые средства ПП, стала Ada (язык Pascal-семейства, предназначен для ответственных систем реального времени и сверхкрупных проектов). Ada предоставляет мощный механизм ПП - активные задачи,
взаимодействующие через рандеву. Задача имеет тело - блок операторов, состояние - инкапсулированные переменные, и точки входа, к которым происходит обращение извне с передачей каких-либо параметров. Обратившаяся к точке входа внешняя задача ожидает, пока задача
войдет в блок обработки точки входа, после чего происходит рандеву, и
далее задачи вновь выполняются независимо. Задачи представляют собой тип данных, их экземпляры могут быть объявлены статически, и
тогда их выполнение начнется при входе в их область видимости, также
экземпляры задач могут создаваться динамически.
Язык параллельного программирования Active Oberon
Новейшее поколение Pascal-языков - это Oberon-семейство (серия
языков и операционных систем, http://www.oberon.ethz.ch). Oberon- 387 -
языки в начале 90-х гг. стали первыми языками компонентного программирования, которые поддерживали триаду "ООП - сборка мусора динамическая загрузка модулей", и вообще первыми компилируемыми
языками со сборкой мусора. Долгое время в Oberon-системах, уникальных во многих других отношениях, отсутствовала полноценная многозадачность, вместо этого использовалась коллективная многозадачность. В 1997 г. был предложен Active Oberon, который был расширен
конструкциями ПП, основанными на идее активных объектов (АО).
В классических Oberon-языках (Oberon, Oberon-2, Component Pascal)
отсутствует специальный объектный тип данных. Объектноориентированные данные описываются в терминах расширяемых записей и связанных с типами процедур (методов) - такой подход компактен
и изящен. В Active Oberon вводится специальный тип OBJECT , который
дополнительно наделяется функциональностью монитора и может
иметь свой поток поведения. Т.е. взаимное исключение, синхронизация
и потоки выполнения объединены в единой абстракции АО.
Методы или блоки операторов с модификатором {EXCLUSIVE} выполняются в режиме взаимного исключения для каждого экземпляра
объектного типа. Оператор AWAIT, вызываемый из такого эксклюзивного блока (ЭБ), переходит к ожиданию истинности условия-аргумента,
снимая блокировку с активного объекта. Перепроверка условий всех
операторов AWAIT выполняется при выходе любого потока из любого
ЭБ экземпляра типа. Т.е. условие в AWAIT задает требуемое состояние
объекта, которое предположительно может достигаться после выполнения другим потоком любого ЭБ. Отличие такой модели от традиционных мониторов состоит в большей абстракции - устранении технических подробностей (условных переменных и явного оператора SIGNAL).
Объекты, обладающие активностью, имеют в своей секции инициализации модификатор {ACTIVE}. В момент создания экземпляра типа будет
запущен отдельный поток, выполняющий секцию BEGIN {ACTIVE} для
объекта. Объект будет существовать до тех пор, пока будет выполняться
его активность, даже если на него более не останется ссылок.
Механизм АО позволяет естественным образом моделировать сложные системы взаимодействующих объектов из любой предметной области. Если при низкоуровневом МП создание и управление потоками в
приложении должно быть централизованным, то в модели АО каждый
объект обладает инкапсулированным поведением и независимым жизненным циклом. Созданный экземпляр объектного типа может быть
просто "выпущен" в систему без централизованного управления. Модель АО вместе со сборкой мусора дает широкие возможности для создания параллельных компонентных систем. На основе Active Oberon
создана исследовательская ОС BlueBottle (http://bluebottle.ethz.ch).
- 388 -
Active BlackBox: активные процедуры и активные
объекты
Подход к задаче
Среда BlackBox Component Builder швейцарской компании Oberon
Miсrosystems, Inc. (http://www.oberon.ch) является перспективной промышленной средой разработки для языка Component Pascal (немного
модифицированный Oberon-2), поддерживающей динамическую загрузку двоичных модулей, сборку мусора, метапрограммирование, включающей расширяемый компонентный каркас для создания графических
интерфейсов, основанных на идеологии составных документов. Таким
образом, среда является мощным инструментом для создания компонентных программных систем прикладного и системного назначения,
успешно используется в таких областях, как научное, бизнеспрограммирование, разработка компиляторов. Официальная поддержка
среды в России осуществляется Интернет-проектом "BlackBox порусски" (см. [1]).
По своей архитектуре среда напоминает мини-операционную систему, в которой нет границ между временем разработки и временем выполнения приложений, состоящих из динамически загружаемых двоичных модулей. Важнейшей проблемой среды являлось отсутствие поддержки многопоточности, которое частично компенсировалось наличием коллективной многозадачности. Альтернативное ядро времени выполнения Active BlackBox было создано нами с целью решить эту проблему и расширить сферы применимости среды до эффективной разработки параллельных многопоточных, а в дальнейшем - распределенных,
приложений. Данная задача распалась на три относительно независимых подзадачи: 1) создание нового ядра времени выполнения (модуль
Kernel), которое включило бы специальный переносимый API для
управления потоками и поддерживало повторную входимость, сборку
мусора, обработку исключений и другие сервисы для многопоточной
версии среды; 2) создание библиотеки высокоуровневых абстракций для
параллельного программирования (модуль AО); 3) введение взаимного
исключения и синхронизации в основные стандартные модули BlackBox
Framework для работы в многопоточной среде.
Новое ядро включает в себя платформенно-независимый API для создания и управления потоками, которые названы задачами (tasks), безопасной приостановки и прерывания задач, для отладки многозадачных
приложений. Переписаны сборщик мусора и диспетчер памяти. Мы
стремились максимально уменьшить эксклюзивные блоки в диспетчере
памяти, и этого удалось достичь - большая часть кода оператора NEW
может выполняться параллельно несколькими задачами, что увеличива- 389 -
ет скорость выделения динамической памяти при выполнении приложений на многопроцессорных машинах.
При разработке высокоуровневых абстракций было решено опираться на модель активных объектов языка Active Oberon, некоторым образом модифицировав ее. Во-первых, абстракции должны были вводиться
на уровне библиотечного модуля, без расширения языка Component
Pascal и модификации компилятора, но в то же время быть удобными
для использования настолько, как будто они являются частью языка.
Решить эту задачу удалось с использованием метаинформации времени
выполнения, в очередной раз показав гибкость и расширяемость
Oberon-платформ. Во-вторых, вместо единственного блока активности,
связанного с объектом, была введена общая концепция активных процедур. Тип данных, имеющий одну или несколько активных связанных
процедур, является полным аналогом активных объектов, при этом может иметь не одну, а несколько активностей, которые могут запускаться
как в момент создания объекта фабрикой, так и в любой момент жизни
экземпляра типа при вызове одной из его связанных процедур. Втретьих, в Active Oberon объект не будет собран сборщиком мусора до
тех пор, пока выполняется его активность. В некоторых случаях это
полезно, но в других может вести к утечкам памяти и процессорного
времени, поскольку для объектов с циклической активностью требуется
явное ее завершение, что порождает проблемы, сходные с явным освобождением памяти в средах без сборки мусора. В Active BlackBox поддерживается два режима работы активных методов - автономный, при
котором ссылка из стека задачи является якорем для объекта и будет
удерживать его до тех пор, пока активная процедура выполняется, и
связанный, при котором ссылка из стека задачи не учитывается сборщиком мусора. Как только на связанный объект не останется внешних
ссылок, его связанная активная процедура будет остановлена, и объект
будет удален.
Чтобы уменьшить число версий среды, третью проблему удалось
решить независимо от нового ядра и внести изменения в основную версию среды таким образом, что новая версия каркаса работает как с ядром Active BlackBox, так и со старой версией ядра. Эти изменения выйдут специальным пакетом обновления BlackBox Service Pack 4 (см. [1])
Был введен специальный модуль Synch, включивший в себя необходимые переносимые примитивы синхронизации - критические секции,
семафоры, локальные блоки данных потоков, а также процедуры взаимного исключения для некоторых сервисов среды, которые в немногозадачной версии среды работают просто как заглушки. Используя модуль
Synch, разработчики могут создавать потокобезопасные модули, которые будут работать без перекомпиляции в обеих версиях среды. Было
- 390 -
решено не адаптировать для многопоточности графические механизмы
ввиду сложности этой задачи. К тому же практика показывает, что графический интерфейс имеет смысл разрабатывать однопоточно. Для выполнения графических операций из задач можно использовать стандартную очередь коллективной многозадачности среды (модуль
Services), помещая в нее отдельные графические операции, которые будут выполнены главным потоком среды. (Аналогичный подход используется в Delphi VCL - процедура Synchronize).
Модуль AО: эксклюзивные блоки
Все высокоуровневые абстракции Active BlackBox собраны в модуле
Ao. Первая из них – тип Ao.MONITOR. Записи, имеющие поля этого типа, становятся мониторами и могут иметь эксклюзивные связанные
процедуры. Чтобы сделать некоторую процедуру эксклюзивной, нужно
вызвать из ее тела процедуру Ao.EXCLUSIVE – вся оставшаяся часть
процедуры от вызова EXLUSIVE до ее конца будет выполнена в режиме
взаимного исключения со всеми другими эксклюзивными процедурами
того же экземпляра типа. Важно, что процедура EXCLUSIVE не требует
никаких параметров и не требует вызова какой-либо закрывающей процедуры. Объект-монитор, к которому она относится, определяется из
стека с помощью метамеханизмов среды, снятие блокировки с объекта
происходит автоматически при выходе из процедуры, вызвавшей
EXCLUSIVE – это достигается подменой адреса возврата в стеке потока
на специальный перехватчик. Процедура проверяет соблюдение всех
необходимых предусловий, т.е. полностью безопасна. Таким образом,
получаем библиотечное средство, по наглядности и надежности эквивалентное конструкциям, вводимым в язык. Процедуру EXCLUSIVE невозможно использовать обходными путями для блокировки какого-либо
объекта «снаружи», невозможно забыть вызвать парную процедуру,
которой просто нет и т.п. Вторая процедура для работы с объектами
мониторами – Ao.AWAIT, которая снимает блокировку с объекта и ожидает, пока какой-либо другой поток не выполнит эксклюзивную процедуру объекта, т.е. пока произойдет предположительное изменение состояния объекта. Ao.AWAIT используется в виде следующей конструкции: WHILE ~ ( условие_которого_ожидаем ) DO Ao.AWAIT END – ее
семантика полностью совпадает с семантикой оператора AWAIT языка
Active Oberon (см. выше). Ниже приведен пример реализации конечного
буфера в Active BlackBox:
MODULE Buffer;
IMPORT Ao;
TYPE
Item* = LONGINT;
Buffer* = POINTER TO RECORD
tag: Ao.MONITOR;
- 391 -
h, n: INTEGER;
data: POINTER TO ARRAY OF Item
END;
PROCEDURE NewBuffer* (size: INTEGER): Buffer;
VAR b: Buffer;
BEGIN
NEW(b); NEW(b.data, size);
RETURN b
END NewBuffer;
PROCEDURE (b: Buffer) Get* (OUT x: Item), NEW;
BEGIN Ao.EXCLUSIVE;
WHILE ~ (b.n # 0) DO Ao.AWAIT END; (* буфер непуст *)
x := b.data[b.h];
b.h := (b.h + 1) MOD LEN(b.data);
DEC(b.n)
END Get;
PROCEDURE (b: Buffer) Put* (x: LONGINT), NEW;
BEGIN Ao.EXCLUSIVE;
WHILE ~ (b.n # LEN(b.data)) DO Ao.AWAIT END; (* буфер неполон *)
b.data[(b.h+b.n) MOD LEN(b.data)] := x;
INC(b.n)
END Put;
END Buffer.
Модуль AО: активные процедуры
Активной процедурой назовем процедуру, тело которой от начала до
конца или от некоторого места до конца выполняется асинхронно, отдельным потоком. При этом активность процедуры должна быть внутренне присущим ей качеством и задаваться в ее определении, а не в
точке ее вызова. Для перевода любой процедуры в активный режим
служит процедура Ao.ACTIVE. При ее вызове происходит следующее:
создается новая задача, в стек которой переносятся все параметры и
локальные переменные процедуры, вызвавшей ACTIVE, затем задача
начинает выполнение процедуры со следующей инструкции после
ACTIVE. При этом в задаче, вызвавшей активную процедуру, она возвращает управление, то есть, имеет эффект RETURN. ACTIVE определяет сигнатуру вызывающей процедуры и выполняет безопасное копирование стека, используя метамеханизмы ядра BlackBox. Особым случаем
являются VAR(IN, OUT)-параметры. Если ACTIVE вызвана с пустым
множеством модификаторов, то проверяется, чтобы все VAR-параметры
ссылались только на глобальные или динамические сущности. Если в
активную процедуру передана ссылка на локальный объект из стека, то
сработает нарушение предусловия. Если ACTIVE вызвана с флагом
copyRefs, то для всех объектов из стека, переданных по ссылке, будут
созданы динамические копии, с которыми и будет работать активная
часть процедуры. Нет никаких ограничений на количество и типы пара- 392 -
метров для активной процедуры. Ниже приведен пример активной процедуры, выполняющей фоновую печать простых чисел:
PROCEDURE PrintSimpleNum* ;
VAR x: LONGINT;
BEGIN
Ao.ACTIVE({});
FOR x := 1 TO MAX(LONGINT) DO
IF CheckSimplify(x) THEN Log.Int(x); Log.Tab END; Ao.Sleep(10)
END
END PrintSimpleNum;
После вызова в среде BlackBox команды PrintSimpleNum она немедленно вернет управление, а запущенная задача будет выполнять в фоне
поиск и вывод в окно протокола простых чисел.
Отладочные средства
В Active BlackBox мы ввели всю необходимую функциональность
для отладки многопоточных приложений. Специальный инструмент
позволяет просмотреть информацию о выполняющихся в среде задачах,
их модулях-владельцах, состоянии, времени отклика для циклических
задач; выполнить безопасную приостановку и остановку задачи в тот
момент, когда она находится внутри своего модуля-владельца; снять
снимок состояния задачи и просмотреть ее стек вызовов процедур, значения переменных, простым щелчком мыши перемещаясь по указателям и полям – метамеханизмы BlackBox позволяют получать на этапе
выполнения всю необходимую информацию о типах и именах. Также
введен механизм автоматического снятия блокировок c объектов при
возникновении исключения внутри EXCLUSIVE-процедуры.
Основное качество среды BlackBox в целом, делающее работу программиста высокоэффективной – гибкость и динамичность, подобная
интерпретирующим средам LISP, Smalltalk и т.п., но при этом 100%компилируемость в быстрый машинный код.
Литература
1. http://blackbox.metasystems.ru – проект «BlackBox по-русски»
2. Muller P.J. The Active Object System Design and Multiprocessor Implementation. Диссертация. Цюрих, ETH, № 14755, 2002.
3. Reali P. Active Oberon Language Report. Цюрих, ETH, 2004.
- 393 -
Download