В.Ш. Кауфман - Языки программирования - концепции и принципы (1990) (1160787), страница 31
Текст из файла (страница 31)
считается достигнутой и происходит рандеву. Оно состоит в том, что после
подстановки аргументов вызова исполняются операторы между do и end (если они
есть). После этого рандеву считается состоявшимся и партнеры вновь
продолжают работать асинхронно.
Оператор отбора входов (в Аде это оператор select) необходим, как уже
говорилось, для обеспечения развязки, чтобы рандеву разных видов не были
жестко зависимы друг от друга. Его главное назначение - учет готовности
клиентов к рандеву (и других условий) с тем, чтобы не ждать рандеву с теми
клиентами, которые "опаздывают" (не готовы к рандеву).
Общий вид этого оператора
select
[ when условие ====> ] отбираемая_альтернатива
последовательность_операторов
or
. . .
or
[ when условие ====> ] отбираемая_альтернатива
последовательность_операторов
[ else последовательность_операторов ]
end select;
Отбираемой альтернативой может быть оператор приема, оператор задержки
или оператор завершения задачи. Когда управление в задаче достигает
оператора отбора, то, во-первых, вычисляются все условия. Те альтернативы,
для которых условие оказалось истинным, считаются "открытыми". Затем среди
открытых альтернатив рассматриваются операторы приема, для которых очередь
вызовов соответствующих входов непуста. Если такие найдутся, то произвольным
(с точки зрения программиста, но не создателя Ада-транслятора) образом
выбирается одна из таких альтернатив, происходит соответствующее рандеву.
Затем выполняется последовательность операторов, расположенная за этой
отобранной альтернетивой, и оператор отбора считается выполненным.
Если среди открытых альтернатив не оказалось операторов приема, готовых
к рандеву, то выполняется оператор задержки (delay) на указанное количество
секунд (если за это время возникает готовность к рандеву у открытых
операторов приема, то отбирается альтернатива, готовая к рандеву, и оператор
отбора завершается как обычно). После задержки и выполнения соответствующей
выбранной альтернативы (accept или delay) последовательности операторов
оператор отбора входов считается выполненным.
Если одной из открытых альтернатив оказался оператор завершения
(terminate), то (если нет готовых к рандеву операторов приема), при
определенных дополнительных условиях задача может быть завершена (до этого
должны, в частности, завершиться запущенные нашей задачей подчиненные
задачи).
Альтернатива "иначе" (else) может быть выбрана, если нет открытых
операторов приема, готовых к рандеву. Если в else стоит задержка, то во
время этой задержки альтернативы уже не проверяются.
В одном операторе отбора, кроме операторов приема (хотя бы один
оператор приема обязателен) допустимы либо только задержки, либо только
завершение, либо только альтернатива "иначе".
В Аде имеются и другие разновидности оператора select, позволяющие не
только мастеру не ждать не готового к рандеву клиента, но и клиенту не
попадать в очередь к не готовому его обслужить мастеру.
6.9. Реализация семафоров, сигналов и мониторов посредством
асимметричного рандеву
Продемонстрируем применение описанных средств управления рандеву на
примерах моделирования посредством рандеву рассмотренных ранее примитивов.
а. Семафоры.
(Спецификацию задачи "семафор" см. выше в п.6.11.)
task body семафор is
begin
loop
accept оградить; -- только синхронизация
accept освободить; -- без обмена - нет части "do"
end loop;
end семафор;
Видно, что из всех богатых возможностей рандеву используется только
синхронизация - нет параметров и тела оператора приема. К тому же
подчеркнута последовательность операций "оградить"-"освободить",
невозможность нарушить их порядок.
б. Сигналы.
task сигнал is
entry послать;
entry ждать;
end сигнал;
task body сигнал is
есть : boolean := false;
begin
loop
select
accept послать; есть := true; -- присваивание -
-- вне рандеву; во время рандеву
-- ничего не делается!
or
when есть => accept ждать; есть := false;
or
delay t; -- задержка на фиксированное время t
end select;
end loop;
end сигнал;
Если нет открытых операторов приема, для которых клиенты готовы, то в
данном случае оператор отбора будет t секунд ждать, не появятся ли клиенты.
Если так и не появятся, считается выполненной последняя альтернатива, а
вместе с ней и весь оператор отбора. Затем - очередной цикл.
Видно, что сигнал применяется для связи разных процессов -
потребовалась развязка, обеспечиваемая оператором отбора. В семафоре она
была не нужна. Ведь сигнал, в отличие от семафора, не ждет на операторе
приема. Он "всегда готов" обслужить любой процесс, но только по входу
"послать".
Только что рассмотрен "незабываемый сигнал". Когда такой сигнал послан,
то мастер о нем помнит до тех пор, пока его не воспримет процесс, ждущий
этого сигнала. Возможна и иная интерпретация сигналов:
task body сигнал is -- забываемый сигнал; спецификация та
begin же, лишь тело другое
loop
accept послать;
select
accept ждать;
else
null; -- если сигнала не ждут,
-- можно о нем забыть
end select;
end loop;
end сигнал;
Рассмотрим еще защиту разделяемой переменной:
task защищенная_переменная is
entry читать (X : out сообщение);
entry писать (X : in сообщение);
end защищенная_переменная;
task body защищенная_переменная is
Z : сообщение;
begin
loop
select
accept читать (X : out сообщение) do X:=Z end;
or
accept писать (X : in сообщение) do Z:=X end;
end select;
end loop;
end защищенная_переменная;
В сущности это монитор, реализующий режим взаимного исключения для
процедур доступа к разделяемому ресурсу. Обратите внимание, что в полной
мере обеспечены синхронизация и исключение, но качество развязки зависит от
реализации языка (формально исполнитель имеет право отбирать, например,
всегда первую альтернативу, даже если "второй" клиент давно ждет).
Лучше в этом смысле работает описанный ниже монитор "буф" (с
дополнительными условиями отбора).
Монитор буфер:
with общий; use общий;
task буф is
entry передать (X : in сообщение); -- Как было!
entry получить (X : out сообщение); -- Пользоваться
end буф; -- так же удобно
-- и надежно
task body буф is
begin
loop
select
when not полон => accept передать (X: in сообщениие) do
занести (X);
end передать;
or
when not пуст => accept получить (X : out сообщение) do
выбрать (X);
end передать;
end select;
end буф;
Итак, мы полностью смоделировали монитор Хансена-Хоара посредством
рандеву. При этом семафоры не нужны, так как взаимное исключение
обеспечивает select; сигналы не нужны благодаря проверке перед accept
(рандеву вида "передать" просто не будет обслужено, пока функция "полон"
вырабатывает логическое значение true). Причем эти проверки происходят в
одном процессе буф, никаких проблем с прерываниями при таких проверках нет.
Таким образом, мы получили то, к чему стремились - асимметричное
рандеву может служить универсальным и надежным средством программирования
параллельных процессов.
6.10. Управление асинхронными процессами в Аде
Рассмотрим частично уже известные сведения об ассимметричном рандеву в
рамках его воплощения в Аде.
Кроме основного примитива-рандеву в ЯП нужен аппарат управления рандеву
(сравните операторы "оградить", "освободить", "послать", "ждать" для ранее
рассмотренных примитивов). В Аде аппарат управления рандеву состоит из
ОБЪЯВЛЕНИЯ ВХОДА (entry), ОПЕРАТОРА ВЫЗОВА ВХОДА (синтаксически неотличимого
от вызова процедуры), оператора ПРИЕМА (accept), оператора ОТБОРА ВХОДОВ
(select) и некоторых других.
[Подчеркнем, что процедуры в Аде все считаются повторно-входимыми (к
ним можно независимо обращаться из различных асинхронных процессов и их тела
могут одновременно исполняться в этих процессах). Входы отличаются от
процедур, во-первых, тем, что обращения к ним из различных процессов
выполняются строго в порядке очереди (именно здесь встроено взаимное
исключение процессов-конкурентов), во-вторых, наличием не одного, а многих
"тел" - операторов приема, расположенных и исполняемых в различных точках
процесса-мастера.]
Оператор ЗАДЕРЖКИ (delay) приостанавливает исполнение задачи, в которой
он находится, на указанный в нем период (реального, астрономического)
времени.
Вызов ВХОДА R, находящийся в задаче К, аналогичен вызову процедуры, но
в общем случае не исполняется немедленно, а лишь "заказывает РАНДЕВУ"
категории R. Это значит, что задача К (клиент по входу R), готова к рандеву
с другой задачей-мастером М, в которой вход R объявлен. Она оказывается
готовой обслужить заказ задачи К лишь тогда, когда достигнет оператора
ПРИЕМА (accept) входа R. Оператор приема предписывает действия, выполняемые
в момент рандеву. Когда эти действия завершаются, рандеву считается
состоявшимся и обе задачи могут продолжать асинхронно работать (до
следующего взаимодействия-рандеву). Если задача М достигает оператора приема
входа R раньше, чем его закажет какая-либо обслуживаемая задача, то задача М
приостанавливается и ждет появления заказов (ждет рандеву).
Таким образом, рандеву происходит тогда (и только тогда), когда и
клиент, и мастер оказываются к нему готовыми (задача К дошла до вызова входа
и заказала рандеву категории R, а задача М дошла до оператора приема и
готова выполнить заказ).
Собственно рандеву состоит в том, что аргументы вызова входа R (из
задачи-клиента) связываются с параметрами оператора приема (из задачи-
мастера) и выполняется тело оператора приема.
Все происходит так, как будто из задачи К обращаются к процедуре R,
объявленной в задаче М. Выполнение оператора приема в задаче М означает тем
самым и выполнение оператора вызова в задаче К (и тем самым завершение
рандеву категории R). Другими словами, задачи К и М как бы сливаются на
время рандеву, а затем продолжают работать независимо до следующего
возможного рандеву.
Оператор ОТБОРА ВХОДОВ (select) позволяет мастеру ожидать сразу
нескольких рандеву и отбирать (из заказанных!) те рандеву, которые
удовлетворяют указанным в этом операторе УСЛОВИЯМ ОТБОРА.
Формально спецификация задачи - это объявление объекта анонимного
задачного типа. Оно связывает имя задачи с объектом, который представляет
асинхронный процесс, определяемый телом соответствующей задачи. Таким
образом данные задачных типов - активные данные. Объект задачного типа, т.е.
асинхронный процесс, запускается (начинает работать) в момент, когда
заканчивается обработка объявления объекта (в нашем случае, объявления
задачи).
В языке можно объявить именованный задачный тип. Например,
task type анализ is
entry прими (Х: in сообщение );
end анализ;
Такое объявление связывает имя "анализ" с задачным типом, класс
значений которого - асинхронные процессы со входом "прими", определяемые
телом задачи с именем "анализ".
В контексте, где доступен задачный тип "анализ", можно объявить
индивидуальную задачу этого типа, например
А: анализ; -- т.е. обычное объявление объекта.
При обработке такого объявления запускается новый асинхронный процесс
типа "анализ" (т.е. создается новый объект задачного типа "анализ") и с ним
связывается имя А. Если нужно, можно запустить и другие процессы этого типа
объявлениями
А1: анализ;
А2: анализ; -- и т.д.
При этом доступ к входу "прими" нужного процесса обеспечивает составное
имя вида А1.прими, А2.прими и т.п.
Когда же имеется единственный процесс со входом "прими", имя задачи
можно не указывать и пользоваться простым именем входа.
Обратите внимание, что входы задач можно рассматривать как аналоги
селекторов в объектах комбинированных типов. Обращение ко входу по
составному имени напоминает выборку значений поля. В определяющем пакете для