Active Oberon

Active Oberon — типобезопасный модульный объектно-ориентированный многопоточный язык программирования общего назначения, разработанный в 1996 — 1997 гг. группой проф. Гуткнехта[de] в Швейцарской высшей технической школе Цюриха (ETHZ) с целью введения в язык Оберон свойств для выражения параллелизма посредством активных объектов[2].

Active Oberon
Изображение логотипа
Класс языка императивный, модульный, объектно-ориентированный, многопоточный, структурный, типобезопасный
Тип исполнения компилируемый, интерпретируемый
Появился в 1997 год
Автор Brinch Hansen, Юрг Гуткнехт (Jürg Gutknecht), Pieter Muller, Patrik Reali
Расширение файлов .Mod, mod
Выпуск 2019
Система типов статическая, сильная
Основные реализации Active Oberon.Net, FOX, PACO, Ronin
Диалекты Zonnon
Испытал влияние MATLAB, Модула-2, Оберон, Object Oberon, Паскаль
Повлиял на Active C#[1], Composita, Go, Zonnon
Сайт a2.inf.ethz.ch
Платформа ARM, Cell, JVM, .NET, x86-32, x86-64
ОС A2, Darwin, Linux, Solaris, Windows

Особенности языка править

Название Active Oberon отражает основную концепцию языка — концепцию Активных Объектов, выраженную в реализации многопоточности и механизмов синхронизации на уровне языка.

Active Oberon расширяет язык Оберон, вводя понятия Объект (Object) и Активность (Activity), связанная с Объектом. Такая связь называется Активным Объектом (Active Object), и означает способность экземпляра объекта иметь активность — собственный поток выполнения[2].

Язык относится к модульным типобезопасным языкам программирования с сильной статической типизацией, допуская в выражениях неявное приведение скалярных типов без потери данных (например, использование целого числа там, где предполагалось использование числа с плавающей точкой).

Модули обеспечивают не только раздельную компиляцию, инкапсуляцию, но и возможность реализации динамической загрузки/выгрузки скомпилированных (объектных) модулей, что используется, например, в операционной системе A2, написанной на этом языке. В некотором роде, аналогом модуля может считаться динамически подключаемая библиотека.

В Активном Обероне, как и в других наследниках Модулы-2, при обращении к сущностям подключенного (импортированного) модуля требуется обязательная квалификация подключенного модуля. Например, если модуль A подключает модуль B и использует переменную v этого модуля, то обращение к переменной должно иметь форму B.v. Иначе говоря, импорт в Активном Обероне не позволяет по умолчанию импортировать из подключенного модуля все экспортируемые им сущности.

Инкапсуляция построена на концепции модуля — все типы, объявленные в модуле, полностью прозрачны друг для друга, а для доступа внешних клиентов требуются спецификаторы доступа. Спецификаторы доступа позволяют экспортировать сущности либо с полным доступом (идентификатор помечается знаком «*»(звёздочка)), либо с доступом «только для чтения» (идентификатор помечается знаком «-» (минус)). Например, конструкция:

TYPE
  Example1* = RECORD x*, y-, z : LONGINT; END;

определяет тип записи (RECORD) Example1, экспортированный за пределы модуля и имеющий три поля с типом «длинное целое», причем поле «x» объявлено со спецификатором доступа «полный доступ», поле «y» объявлено со спецификатором «доступ только для чтения» и поле «z» является скрытым полем, недоступным внешним клиентам.

Язык поддерживает полиморфизм, перегрузку операций (для структурных типов), делегаты, совместимые как с методами, так и с процедурами. Объектными типами являются RECORD и ссылочный тип OBJECT. Они могут иметь методы и операции. У типа OBJECT могут быть тело и активность. Все методы являются виртуальными. Множественное наследование отсутствует, вместо него используется концепция множественного наследования интерфейсов (DEFINITION в терминах языка).

Синтаксис и семантика править

Синтаксис языка в процессе развития практически не изменяется — разработчики предпочитают уточнять семантику уже имеющихся синтаксических конструкций с помощью вводимых семантических модификаторов, что позволяет исключить значительный объём правок при введении новой функциональности, упростить компилятор, сделав его код более доступным для понимания и модификации, а также обеспечить легкость изучения и использования языка. Модификаторы заключаются в фигурные скобки {} после имени переменной, типа или ключевого слова. Например, конструкция:

VAR
  Example2 : PROCEDURE {REALTIME, C} ( VAR low, high: LONGINT ): BOOLEAN;

объявляет процедурную переменную Example2, указывающую на процедуру реального времени, с соглашением о вызове CCALL, принимающую два параметра типа длинное целое и возвращающую значение логического типа.

Описание объекта, в целом, соответствует описанию модуля, за исключением синтаксиса заголовка и отсутствия секции IMPORT. Методы целиком описываются внутри описания объекта, операции, за редким исключением, могут описываться вне тела объекта. У объекта может быть произвольное количество инициализаторов и не более одного финализатора. Встроенная процедура NEW, использующаяся для создания переменных ссылочного типа, вызывает инициализатор, выбираемый компилятором по сигнатуре параметров. Инициализатор помечается знаком & перед именем метода. Финализатор — метод без параметров, перед именем которого стоит знак ~, — вызывается автоматически при утилизации объекта.

Последовательность операторов, заключенная в операторные скобки BEGIN END, называется блоком операторов. Блок операторов также может содержать список модификаторов и секцию гарантированного завершения (FINALLY).

Переменная, а также поле записи или объекта, могут быть инициализированы константным выражением при объявлении:

TYPE
    Point = RECORD
        x := 0,
        y := 0  : LONGINT;
    END;

VAR
    i := 0, j := 10, k := 100 : INTEGER;
    Point : Point; (* поля x, y записи инициализированы значением 0 *)

Типы данных править

Язык предлагает богатый набор встроенных типов:

  • Базовые типы
    • логический: BOOLEAN;
    • символьные: CHAR8, CHAR16, CHAR32 и псевдоним CHAR для символьного типа по умолчанию;
    • целочисленные знаковые: SIGNED8, SIGNED16, SIGNED32, SIGNED64 и псевдонимы SMALLINT, INTEGER, LONGINT, HUGEINT;
    • целочисленные беззнаковые: UNSIGNED8, UNSIGNED16, UNSIGNED32, UNSIGNED64;
    • вещественные: REAL, LONGREAL;
    • комплексные: COMPLEX, LONGCOMPLEX;
    • машинозависимые: SIZE, ADDRESS;
    • множество: SET, SET8, SET16, SET32, SET64;
    • расширяемые перечисления: ENUM;
  • структурные
    • массивы: ARRAY — статические, динамические, открытые, математические;
    • расширяемые структуры: RECORD;
    • объектные: OBJECT;
  • Специальные
    • процедурные;
    • делегаты: процедурные переменные, совместимые как с процедурами, так и с методами;
    • типизированные указатели на структурные типы;
    • обобщенные указатели: ANY, OBJECT, ARRAY;
  • системные: SYSTEM.BYTE

Многопоточность править

Для Активного Оберона реализовано две модели многопоточности, основанные на работах Бринча Хансена и Тони Хоара[3]:

Исходный код, написанный с использованием синтаксиса блокирующих примитивов синхронизации языка Active Oberon, может использоваться для обеих моделей многопоточности — компилятор будет генерировать нужный для конкретной модели код. При таком подходе нет нужды в переписывании программного обеспечения под разные модели. Лишь небольшие участки исходного кода требуют адаптации (например, обработка прерываний), чтобы подавить автоматическую генерацию переключений в данном участке машинного кода. Для этого блок операторов помечается модификатором {UNCOOPERATIVE}.

Активные Объекты править

Поток инкапсулирован в объекте и, являясь его неотъемлемой частью, создаётся в момент инстанцирования активного объекта. Для указания активности объекта его тело помечается модификатором ACTIVE.

После размещения экземпляра объекта выполняется инициализатор (если есть), затем тело объекта (если есть). Если объект помечен как активный, создается активность, в которой асинхронно выполняется тело объекта, в противном случае выполнение производится синхронно в том потоке, в котором был создан экземпляр.

Активность объекта завершается после завершения исполнения тела объекта. Пока исполняется тело, объект продолжает существовать, даже если на него нет ссылок. После этого объект становится пассивным и может быть утилизирован в соответствии с обычными правилами.

Пример описания и использования активного объекта:

MODULE Example3;

TYPE
  ActiveObject = OBJECT
  VAR state : SET;

    PROCEDURE &New;
    BEGIN
      state := {};
    END New;

    PROCEDURE &Init*( state : SET );
    BEGIN
      SELF.state := state;
    END Init;

    PROCEDURE ~Finalize;
    BEGIN
    ...
    END Finalize;

  BEGIN {ACTIVE}
    ...
  END ActiveObject;

VAR
  object : ActiveObject;

BEGIN
  NEW( object );
  object.Init( {0..7, 9, 12, 30..31} );
  NEW( object, {} );
END Example3.

Межпроцессное взаимодействие править

Вызовы методов совместно используемых активных и неактивных объектов действуют как коммуникационный механизм между активными объектами. Поскольку активные объекты существуют в многопоточной среде, предоставляется механизм для координирования параллельного доступа к их состоянию. Взаимодействие посредством обмена сообщениями может осуществляться с использованием специальных программных каркасов[5].

Защита от одновременного выполнения править

Блок операторов, помеченный модификатором EXCLUSIVE называется монопольной областью (exclusive region). Монопольная область в Активном Обероне соответствует концепции критической области Хансена[6]. Если монопольная область охватывает всё тело метода, то он называется монопольным методом (exclusive method) и совмещает концепцию Хансена с процедурой монитора Хоара[7][8]. В отличие от процедуры монитора, метод объекта не обязан быть монопольным — в этом случае он может наблюдать несогласованные состояния объекта. В монопольной области может находиться не более одной активности одновременно.

Таким образом, модель защиты в Активном Обероне — монитор, размещенный в экземпляре объекта (instance-based monitor). Модуль считается объектным типом с единственным экземпляром (singleton instance) и его процедуры тоже могут быть монопольными, защищая модуль в целом.

Главная идея монитора (и активного объекта) в том, что с монитором связывается некий инвариант — выражение, определяющее непротиворечивое внутреннее состояние объекта и доказывающее правильность его поведения[9]. Инициализаторы и монопольные методы являются инструментами, предоставляемыми языком, для поддержания инвариантов объекта и сокрытия его внутреннего состояния. Инициализатор объекта устанавливает его инвариант, а монопольные методы его поддерживают. Когда понятие монитора объединено с понятием модуля, формируется мощный механизм для структурирования операционных систем[10][11][12].

Пример использования монопольной секции:

(* Процедуры Set и Reset взаимно исключаемы *)
TYPE
  MyContainer = OBJECT
    VAR x, y: LONGINT; (* Инвариант: y = f(x) *)

    PROCEDURE Set(x: LONGINT);
    BEGIN {EXCLUSIVE} (* изменение x и y атомарно *)
      SELF.x := x; y := f(x)
    END Set;

    PROCEDURE Reset;
    BEGIN
      ...
      BEGIN {EXCLUSIVE} (* изменение x и y атомарно *)
        x := x0; y := y0;
      END;
      ....
    END Reset;
  END MyContainer;

Синхронизация править

В отличие от большинства реализаций мониторов, использующих для синхронизации условные переменные Хоара[8] (на основе очередей событий Бринча Хансена[6]), синхронизацию активностей обеспечивает оператор AWAIT, принимающий в качестве аргумента логическое выражение — условие продолжения выполнения программы в теле объекта. Чтобы гарантировать правильность синхронизации, AWAIT должен находиться в монопольной области. В случае невыполнения условия продолжения, AWAIT приостанавливает (suspend) активность и, если находится в монопольной области, отпускает захваченную область на время приостановки, что позволяет другим активностям изменить состояние объекта и сделать условие продолжения истинным. Приостановленная активность продолжит работу только в том случае, если сможет повторно войти в монопольную область.

Пример синхронизации внутри разделяемого буфера:

TYPE
  Synchronizer = OBJECT
    VAR awake: BOOLEAN

    PROCEDURE Wait;
    BEGIN {EXCLUSIVE}
      AWAIT(awake);
      awake := FALSE;
    END Wait;

    PROCEDURE WakeUp;
    BEGIN {EXCLUSIVE}
      awake := TRUE;
    END WakeUp;
  END Synchronizer;

Обработка ошибок и исключительных ситуаций править

В Активном Обероне отсутствуют средства структурной обработки исключительных ситуаций — они обрабатываются централизовано средой времени выполнения.

Оператор ASSERT принимает в качестве обязательного аргумента логическое выражение, в случае нарушения которого происходит прерывание программы и, также, как и при выполнении оператора безусловного останова HALT, управление передаётся в централизованный обработчик исключений. Если условие оператора ASSERT может быть вычислено на этапе компиляции, то, в случае невыполнения условия генерируется ошибка компиляции. Оператор ASSERT и HALT могут иметь необязательный параметр — спецификатор исключения, который может быть проанализирован обработчиком.

После обработки исключения управление передаётся в ближайшую по стеку вызовов секцию гарантированного завершения FINALLY, если она есть. Затем, если тело активного объекта помечено модификатором SAFE, будет произведён перезапуск активности, в противном случае работа активности завершается.

Среда времени выполнения править

Информация о типах времени выполнения править

Только структурные типы (массивы, записи, объекты) имеют информацию о типах времени выполнения (метаинформацию). Метаинформация хранится в специальной структуре, называемой Дескриптор типа. Дескриптор типа содержит данные о типах и именах переменных и полей, таблицу наследования, таблицу виртуальных методов и таблицы реализованных интерфейсов.

Управление памятью править

В Активном Обероне применяется автоматическое управление памятью с использованием прерываемого (вытесняемого) сборщика мусора реального времени[13], основанного на методе пометок (Mark and Sweep). Сборщик мусора выполняется в отдельном потоке, и активности (потоки), имеющие приоритет выше, чем приоритет активности сборщика мусора, могут приостановить его выполнение. При этом дерево объектов замораживается. На текущий момент только сущности реального времени могут прерывать активность сборщика мусора, в них запрещено динамическое выделение памяти, за этим следит компилятор.

Управление памятью основано на использовании типизированных участков памяти. Такой участок хранит указатель на дескриптор типа. Используя информацию о типах времени выполнения, находящуюся в дескрипторе типа, сборщик мусора находит переменные и поля ссылочного типа и помечает блоки, на которые они указывают. Другими словами, сборщику мусора нет необходимости проверять каждое значение, похожее на указатель, является ли оно корректным указателем в куче — используя информацию, предоставляемую дескриптором типа, он точно знает, какие элементы обрабатывать, что существенно увеличивает скорость и точность работы и снижает нагрузку на процесс сборки мусора. Для ускорения выделения памяти свободные участки помещаются в Списки свободных блоков, содержащие блоки памяти определенных размеров.

Переменные ссылочного типа, помеченные модификатором UNTRACED относятся к нетрассируемым указателям. Такие указатели не отслеживаются сборщиком мусора и участки памяти, на которые они ссылаются, могут быть утилизированы в любой момент времени, в случае, если они были выделены средой времени выполнения и на них нет достижимых ссылок, учитываемых сборщиком мусора. Часто, такие модификаторы используются для работы с участками памяти, выделенными вне среды времени выполнения Активного Оберона, или с небезопасными указателями.

Управление активностью объектов править

Среда времени выполнения отвечает за распределение процессорного времени, гарантирует (совместно с компилятором), что в монопольной области находится не более одной активности, обеспечивает своевременную проверку условий оператора AWAIT и возобновление работы приостановленных активностей. Выражения условий продолжения внутри объекта пересчитываются во всех точках выхода из монопольных областей. Это означает, что изменение состояния объекта вне монопольной области не приводит к пересчёту условий. Когда несколько активностей соревнуются за одну и ту же монопольную область, то активности с выполненными условиями рассматриваются раньше тех, которые только хотят войти в защищенную область[3][14].

Подключение и инициализация модулей править

В Активном Обероне отсутствуют средства для прямого управления динамической загрузкой и выгрузкой модулей. Язык предлагает лишь секцию импорта (IMPORT), в которой указан список подключаемых модулей и секцию инициализации модуля. Среда времени выполнения должна обеспечить подключение и инициализацию подключаемых модулей до инициализации текущего модуля. Динамическое подключение модуля осуществляется через механизм динамического связывания. Модуль нельзя выгрузить до тех пор, пока на него есть ссылка из списка подключения другого загруженного модуля. Таким образом, чтобы исключить проблему циклических ссылок, модули должны выгружаться в порядке, обратном порядку загрузки.

Обработка ошибок и исключительных ситуаций править

Модуль Traps обеспечивает централизованную обработку исключительных ситуаций. Параметры, принимаемые операторами ASSERT и HALT, могут использоваться для классификации исключительной ситуации.

После обработки исключительной ситуации в модуле Traps среда времени выполнения осуществляет поиск секций гарантированного завершения FINALLY и передает на них управление для выполнения завершающих операций.

В случае, если активность помечена модификатором SAFE и в теле объекта отсутcтвует секция FINALLY, производится перезапуск активности, иначе выполнение активности завершается.

Программный интерфейс среды выполнения предоставляет возможность установки собственного обработчика исключительных ситуаций.

Расширения языка править

Примеры программ править

Hello, World! править

MODULE HelloWorld;
IMPORT KernelLog;

BEGIN
  KernelLog.String( "Hello, World!" );
END HelloWorld.

Решение классической задачи поставщика и потребителя править

MODULE BoundedBuffers;
TYPE
  Item* = OBJECT;

  Buffer* = OBJECT
    VAR
      h, n: INTEGER;
      B: ARRAY * OF Item;

    PROCEDURE Get*(): Item;
    VAR x: Item;
    BEGIN {EXCLUSIVE}
      AWAIT(n # 0); (* буфер не пуст *)
      x := B[h]; h := (h+1) MOD LEN(B); DEC(n);
      RETURN x
    END Get;

    PROCEDURE Put*(x: Item);
    BEGIN {EXCLUSIVE}
      AWAIT(n # LEN(B)); (* буфер не полон *)
      B[(h+n) MOD LEN(B)] := x; INC(n)
    END Put;

    PROCEDURE &Init(max: INTEGER);
    BEGIN (* инициализатор *)
      NEW(B, max); h := 0; n := 0
    END Init;
  END Buffer;

END BoundedBuffers.

Ссылки править


См. также править

Примечания править

  1. Страница проекта Active C# Архивировано 4 сентября 2011 года.
  2. 1 2 J. Gutknecht. Do the Fish Really Need Remote Control? A Proposal for Self-Active Objects in Oberon., 1997.
  3. 1 2 3 P.J. Muller. Active Object System. Design and Multiprocessor Implementation. — ETH Zurich, 2002., Diss. ETH № 14755.
  4. Florian Negele. Combining Lock-Free Programming with Cooperative Multitasking for a Portable Multiprocessor Runtime System, ETH Zurich, 2014, Diss. ETH №. 22298
  5. Florian Negele. A2 Concurrency Framework, ETH Zurich, June 3, 2009
  6. 1 2 P. Brinch Hansen. Structured Multiprogramming. Communications of the ACM, 15(7), July 1972
  7. P. Brinch Hansen. Operating System Principles. Prentice-Hall, 1973.
  8. 1 2 C.A.R. Hoare. Monitors: An Operating System Structuring Concept. Communications of the ACM, 17(10):549-557, October 1974.
  9. O. J. Dahl. Monitors Revisited. In A.W. Roscoe, editor, A Classical Mind — Essays in Honour of C.A.R. Hoare. Prentice-Hall,
    1994.
  10. J.L. Keedy. On Structuring Operating Systems With Monitors. ACM Operating Systems Review, 13(1), January 1979.
  11. N. Wirth. Modula: A Language for Modular Multiprogramming. Software — Practice and Experience, 7:3-35, 1977.
  12. N. Wirth. The Use of Modula. Software — Practice and Experience, 7:37-65, 1977.
  13. Ulrike Glavitsch. Real-time Garbage Collection in A2. Institute of Computer Systems, ETH Zurich
  14. P. Reali. Active Oberon Language Report. — ETH Zurich, 2004.