Ада-95. Компилятор GNAT

         

Защищенные входы и барьеры


По аналогии со входами задач, защищенный модуль может иметь защищенные входы.

Действия, выполняемые при вызове защищенного входа, предусматриваются в его теле.

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

Однако внутри тела защищенного модуля защищенные входы предохраняются логическим выражением, которое называют барьером, а результат вычисления этого логического выражения должен иметь предопределенный логический тип Standard.Boolean.

Если при вызове защищенного входа значение барьера есть False, то выполнение вызывающей задачи приостанавливается до тех пор, пока значение барьера не станет равным True

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

Следовательно, вызов защищенного входа может быть использован для реализации условной синхронизации.

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

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

Хорошим примером решения упоминавшейся ранее проблемы "поставщик-потребитель" служит реализации циклического буфера с помощью защищенного типа:

-- спецификация защищенного типа protected type Bounded_Buffer is

entry Put(X: in Item); entry Get(X: out Item);

private

A: Item_Array(1 .. Max); I, J: Integer range 1 .. Max := 1; Count: Integer range 0 .. Max := 0;

end Bounded_Buffer;



-- тело защищенного типа protected body Bounded_Buffer is

entry Put(X: in Item) when Count < Max is

begin

A(I) := X; I := I mod Max + 1; Count := Count + 1; end Put;

entry Get(X: out Item) when Count > 0 is

begin

X := A(J); J := J mod Max + 1; Count := Count - 1; end Get;

end Bounded_Buffer;

<


Здесь предусмотрен циклический буфер, который позволяет сохранить до Max

значений типа Item.

Доступ обеспечивается с помощью входов Put и Get.

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

declare

. . . My_Buffer: Bounded_Buffer; . . . begin

. . . My_Buffer.Put(X); . . . end;

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

. . . select

My_Buffer.Get(X); . . . -- список инструкций else

. . . -- список инструкций end select;

Поведение защищенного типа контролируется барьерами.

При вызове входа защищенного объекта выполняется проверка соответствующего барьера.

Если значение барьера False, то вызов помещается в очередь, подобно тому, как это происходит при вызове входа задачи.

При описании переменной My_Buffer

буфер - пуст, и, таким образом, барьер для входа Put имеет значение True, а для входа Get - False.

Следовательно, будет выполняться только вызов Put, а вызов Get

будет отправлен в очередь.

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

Таким образом, после завершения обработки первого же вызова Put, если вызов Get уже находится в очереди, то значение барьера для Get будет вычислено заново, и это позволит обслужить ожидающий в очереди вызов Get.

Важно понять, что здесь нет задачи, которая непосредственно ассоциирована с самим буфером.

Вычисление барьеров эффективно выполняется системой времени выполнения.

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



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

Такие правила гарантируют эффективность реализации защищенного объекта.

Следует обратить особое внимание на то, что барьер может ссылаться на значение глобальной переменной.

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

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

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

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

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

Это имеет одно важное следствие: если состояние защищенного объекта изменяется, и существует задача, которая ожидает новое состояние защищенного объекта, то такая задача получает доступ к ресурсу.

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

Таким образом, полностью предотвращаются неудовлетворенные опросы и состязание задач за ресурс.



Основопологающая концепция защищенных объектов подобна мониторам.

Они являются пассивными конструкциями с синхронизацией, предусматриваемой системой времени выполнения языка.

Однако защищенные объекты, по сравнению с мониторами, обладают большим преимуществом: протокол взаимодействия с защищенными объектами описывается барьерными условиями (в правильности которых достаточно легко убедиться), а не низкоуровневыми и неструктурируемыми сигналами, используемыми в мониторах (как в языке Modula).

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

Защищенные модули позволяют осуществлять очень эффективную реализацию различных сигнальных объектов, семафоров и подобных им парадигм:

-- спецификация защищенного типа protected type Counting_Semaphore (Start_Count : Integer := 1) is

entry Secure; procedure Release; function Count return Integer;

private

Current_Count : Integer := Start_Count;

end;

-- тело защищенного типа protected body Counting_Semaphore is

entry Secure when Current_Count > 0 is

begin

Current_Count := Current_Count - 1; end;

procedure Release is

begin

Current_Count := Current_Count + 1; end;

function Count return Integer is

begin

return Current_Count; end;

end Counting_Semaphore;

Особенностью этого примера является то, что Start_Count является дискриминантом.

При вызове входа Secure опрашивается барьер этого входа.

Если результат опроса барьера есть False, то задача ставится в очередь, а ожидание обслуживания становится True.

Следует заметить, что этот пример демонстрирует реализацию общего семафора Дейкстры (Dijkstra), где вход Secure и процедура Release

соответствуют операциям P и V (Dutch Passeren и Vrijmaken), а функция Count возвращает текущее значение семафора.

Очевидно, что в очереди обращения к защищенному входу может одновременно находиться несколько задач.

Так же как и в случае с очередями к входам задач, очереди к входам защищенных объектов обслуживаются в порядке поступления вызовов (FIFO - First-In-First-Out).

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

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

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

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


Содержание раздела