3.2. Структуры тел задач

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

task body Typical_Task is
--
-- Здесь при необходимости объявляются ресурсы, доступные данной задаче.
-- Например,
-- объявления типа
-- объявления переменных
-- объявления подпрограмм
-- объявления пакетов
--
begin
--
-- Последовательность операторов, выполняемых после активизации данной
-- задачи (и после определения ресурсов, в разделе "объявлений").
--
end Typical_Task;

        Рис. 3.4. Скелетная структура тела задачи.

Объявления могут включать в себя объявления типа, экземпляров переменных тех или иных типов (чьи объявления типа, перечисляемые в модулях, становятся видимыми через список with данной задачи) и т. д. Все эти объявления локально доступны из операторов, заключенных в begin ... end в теле задачи.

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

3.2.1. Задачи, не являющиеся ни обслуживателями, ни залросчикамн

В теле задачи, не являющейся ни запросчиком, ни обслуживателем, разрешается использовать любую допускаемую в теле подпрограммы последовательность операторов, включая операторы, обладающие возможностью порождать другие задачи, однако, исключая операторы, чья семантика предполагает выдачу запроса, получение услуг от других задач или предоставление услуг другим задачам. После выполнения последней последовательности begin ... end в теле задачи или выполнения оператора terminate задача становится кандидатом на прекращение своей работы. Если задача не порождала никаких других задач или же все порожденные ею задачи завершили свою работу, то ее выполнение немедленно прекращается. В противном случае задача переводится в состояние ожидания, ожидая завершения работы всех порожденных ею задач. Это правило завершения работы распространяется на все задачи на языке Ада вне зависимости от их функций.

3.2.2. Задачи-запросчики

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

Poirtfolio_Server.Print_winners(spread -> 15);

выданное, например, задачей Mernber(5) (см. рис. 2.5), интерпретируется как запрос на предоставление услуг от Portfolio_Server, предполагающий печать списка всех вкладов, каждый из которых имеет стоимость, по крайней мере на 15% превышающую его закупочную цену.

Затем вызывающая программа Mernber(5) переводится в состояние ожидания, ожидая рандеву c обслуживающей программой до тех пор, пока последняя не возвратит сигнал подтверждения и этим завершит рандеву. Мы рассмотрим природу и синхронизацию таких подтверждений в последующих параграфах. После получения подтверждения (сравнимого, но не идентичного с возвратом управления из вызванного пакета) вызывающая программа продолжает свое выполнение параллельно с обслуживающей. В общем случае каждое обращение задачи-запросчика к обслуживателю приводит к ее временной задержке (блокировке) в ожидании рандеву с обслуживателем.

3.2.3. Задачи-обслуживатели

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

1. Семантика видимого оператора приема:

Семантика появляющегося в задаче Т оператора accept

accept Е(...) do S end

может быть разбита на два случая:

  1. если другая задача U, блокированная в текущий момент, производила обращение ко входу задачи Е(. . .), то задача Т продолжает свою работу, выполняя операторы из последовательности операторов S, или
  2. если никакая другая задача не осуществляла обращение к задаче Е(. . .), то задача Т блокируется до тех пор, пока другая задача U не выполнит обращение Е(. . .), после чего задача Т немедленно возобновляет свою работу, выполняя операторы из последовательности операторов S.

В обоих случаях задача U блокируется с того момента, как она произвела обращение Е(. . .), и вплоть до момента, когда задача Т завершит выполнение последовательности операторов S, после чего обе задачи продолжают свое выполнение параллельно. Например, предположим, что приводимый ниже оператор приема accept появляется в теле задачи Portfolio_Server:

accept Print_winners(
   spread: in percent);
do
   --
   -- Последовательность операторов,
   -- реализующая желаемый результат.
   --
end Print_winners;

После передачи управления на оператор accept немедленно начинает выполняться заключенная в do...end последовательность операторов, если к Portfolio_Server.Print_winners было сделано по крайней мере одно обращение. Если число обращений превышает единицу, то последующие обращения помещаются в очередь, организованную по принципу "первым пришел - первым обслужен", которая связана со входом Portfolio_Server.Print_winners. Когда управление достигает вышеописанного оператора accept, ожидающее обслуживания обращение ко входу удаляется из очереди в указанном ранее порядке. Таким образом, оператор accept всегда обслуживает наиболее долго задержанное необслуженное обращение ко входу.

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

Аппаратные средства i432 не обеспечивают отдельную аппаратную поддержку для имеющегося в языке Ада механизма рандеву. Однако компилятор с языка Ада применяет имеющиеся в системе i432 операции для работы с блочно-ориентированными сообщениями, используя их в качестве примитивов при построении механизма рандеву. Из вышесказанного можно видеть, что открытый оператор accept можно использовать в задаче-обслуживателе только в том случае, если задача-обслуживатель не будет выполняться раньше оператора accept до момента выдачи к ней соответствующего обращения. Руководствуясь этим принципом, рассмотрим в этом контексте нашу задачу с портфелем.

accept President_delete(
   name:        in  string_of 30;   -- Текст имени экземпляра портфеля.
   member_name: in  string_of 30;
   check:       out boolean)        -- Проверка; принеудаче возвращается
                                    -- false.
   do
      -- Операторы для определения и установления проверки на соответствие
      -- разрешенному значению.
   end President_delete;

accept Vice_pres_delete(
   name:        in  string_of 30;    -- Текст имени экземпляра портфеля.
   member_name: in  strmg_of 30;
   check:       out boolean)         -- Проверка; при неудаче возвращается
                                     -- false.
   do
      -- Операторы для определения и установления проверки на соответствие
      -- разрешенному значению.
   end Vice_pres_delete;

accept Secretary_delete(
   name:        in  string_of 30:    -- Текст имени экземпляра портфеля.
   member-name: in  string_of 30;
   check:       out boolean)         -- Проверка; при неудаче возвращается
                                     -- false.
   do
      -- Операторы для определения и установления проверки на соответствие
      -- разрешенному значению, за которым следует последовательность
      -- операторов, удаляющих заданный экземпляр портфеля, указанный три раза
      -- (в данном и двух предыдущих обращениях).
   end Secretary_delete;
   
   Рис. 3.5. Пример использования открытых операторов accept:
             обслуживающая часть протокола по удалению экземпляра портфеля.

Предположим, что пакет Club_Portfolio включает в себя операции, допускающие удаление портфеля акций организации. Далее предположим, что правило, по которому разрешается удаление экземпляра портфеля, таково, что в проведении операции удаления должны участвовать три представителя администрации организации, выдавая три независимых запроса на удаление портфеля к Portfolio_Server. Portfolio_Server позволяет выполнять серию из трех операторов accept, каждый из которых обслуживает один запрос на удаление от одного из трех представителей администрации (например, президента, вице-президента и секретаря). Порядок следования этих запросов не должен оказывать влияния на работу Portfolio_Server. На рис. 3.5 приводится возможный вариант программы для задачи Portfolio_Server, позволяющий кооперативное удаление экземпляров портфеля. Два последних оператора accept в серии должны быть обязательно открытыми, даже если первый является альтернативным. [Полный текст этого фрагмента для части тела приведен в приложении Е. В этом приложении предполагается, что пакет Club_Portfolio отличается от описанного в гл. 2 и приводимого в приложении В наличием двух дополнительных общедоступных операций: Delete_folio и Create_folio. В скорректированной версии портфель акций капиталовложений организации создается и удаляется явным образом членами клуба, имеющими соответствующие привилегии.]

Рассмотрим, например, ситуацию, в которой секретарь организации, отказывается (или забывает) выдать запрос на удаление к активизированному экземпляру Portfolio_Server, пытающемуся удалить заданный портфель. Задача Т (например, Task_Master), вызвавшая активизацию Portfolio_Server, будет бесконечно долго ожидать от секретаря необходимых действий. Одним из способов разрешения такой проблемы является введение временного ограничения. При этом Portfolio_Server разрешается быть активным не более заданного интервала времени, после чего задача Т прервет Portfolio_Server, указав, что работа Portfolio_Server была прервана из-за превышения лимита времени. Для определения того, является ли Portfolio_Server активным, задача Т может использовать атрибут Portfolio_Server'TERMINATED. Если, помимо ожидания завершения работы Portfolio_Server, у задачи Т нет никакой другой работы, то она может на короткие промежутки времени "усыплять" сама себя, воспользовавшись для этого оператором языка Ada delay. При этом задача Т не будет расходовать процессорное время, выполняя "активное ожидание" для Portfolio_Server. Наконец, если задача Т определит, что время работы Portfolio_Server превысило допустимое, она немедленно прервет его выполнение при помощи оператора

abort Portfolio-Server;

Подход, обеспечивающий координированное удаление портфеля и проиллюстрированный на рис. 3.5, обусловлен желанием минимизировать блокировку более "важных" задач (President и Vice_President) в процессе выдачи ими соответствующих запросов на удаление. Однако важность этой блокировки может оказаться не принципиальной. Вместо этого более важным может стать обеспечение синхронизации всех трех запросов на удаление, с тем чтобы предотвратить "забегание вперед" любого из членов администрации организации, до тех пор, пока все они не выдадут запрос на удаление. Приведенные на рис. 3.5 операторы accept могут быть вложенными, обеспечивая этим требуемую синхронизацию. В общем случае структура с вложенными операторами accept в задаче-обслуживателе позволяет этой задаче выступать в качестве синхронизатора двух или более задач-запросчиков.

2. Семантика операторов accept, вложенных в операторы select: Несмотря на то что имеется бесчисленное множество случаев, подобных проиллюстрированным на рис. 3.5, при которых задача-обслуживатель вынуждена пребывать в состоянии ожидания (не имея никакой другой полезной работы), использование оператора accept не всегда является приемлемой стратегией. Обычно задача-обслуживатель может отвечать на два или более различных запроса, пли же выполнять некоторые другие действия в отсутствие обращения к заданному оператору select. В каждом из этих случаев имеется выбор среди нескольких альтернатив, сокращающий время ожидания рандеву задачей в тот период времени, когда другая работа задерживается и не может быть инициирована немедленно. Такой тип управляемого выбора поддерживается при помощи оператора ожидания по выбору.

Аналогично оператору case оператор ожидания по выбору управляет выбором из набора альтернативных кодовых последовательностей. Если в случае использования оператора case выбор требуемой альтернативы определяется просто значением переменной выбора, то выбор альтернативы в операторе ожидания по выбору гораздо более сложен. В операторе ожидания по выбору выбор делается на основании рассмотрения целого ряда факторов, включая (что в данном случае является наиболее важным) анализ того, какой из альтернативных операторов accept имеет задержанное обращение. На рис. 3.6 приведен синтаксис оператора ожидания по выбору внутри тела задачи. Приводимая структура является прототипом для большого класса задач-обслуживателей.

begin
   loop
      select
         {when <булевское выражение для Service_1>->}
         accept Service_1(. . .) do
            -- Последовательность операторов.
         end Service-1;
         -- Здесь может быть расположена последовательность операторов для
         -- выполнения Service_1.
      or {when <булевское выражение для Service_2->}
         accept Service_2(. . .) do
            -- Последовательность операторов.
         end Service-2;
         -- Здесь может быть расположена последовательность операторов для
         -- выполнения Service_2.
      or {when <булевское выражение для Service_3->}
         accept Service_3(. . .) do
            -- Последовательность операторов.
         end Service_3;
         -- Здесь может быть расположена последовательность операторов для
         -- выполнения Service_3.
      else
         -- Последовательность операторов.
      end select;
   end loop;
end;     -- Конец тела задачи.

Рис. 3.6. Цикл ожидания по выбору для тела задачи. (Элементы
          в фигурных скобках не являются обязательными.)

Поведение обслуживающей задачи такого типа следующее: после активизации задача входит в бесконечный цикл (операторные скобки loop . . . end). Повторно выполняющийся оператор ожидания по выбору представлен операторными скобками select . . . end. Этот оператор в свою очередь задает набор альтернативных операций, которые должны состоять по крайней мере из одного оператора select. На рисунке приводятся четыре возможных варианта: три оператора accept и обусловливаемые ими (необязательные) последовательности операций, за которыми следует оператор else. Перед любым оператором accept может быть дополнительно указано условие, задаваемое в предложении when и имеющее форму:

when <булевское выражение> ->

Оператор select, заданный с условием when, выбирается для выполнения только в том случае, если выражение, заданное в условии when и вычисляемое в начале выполнения оператора ожидания по выбору при рассмотрении различных альтернатив, оказывается истинным. В терминологии языка Ада альтернатива считается открытой (т. е. доступной для выбора), если она не содержит условия или же если заданное в ней условие истинно. В противном случае альтернатива считается закрытой. Оператор select выбирает для выполнения фрагмент программы на основе заданных в предложениях when логических условий, а также задержанных обращений. В начале выполнения оператора select каждая из альтернатив анализируется и выбирается подмножество открытых альтернатив, одна из которых затем выбирается для выполнения. Если только одна альтернатива А имеет задержанный запрос, то выполняется фрагмент программы, относящийся к альтернативе А. Если задержанные запросы имеют несколько альтернатив, то выбор делается из набора этих альтернатив. В этом случае ответственность за проведение оптимального выбора ложится на систему, поддерживаемую языком Ада. Наконец, если ни одна из открытых альтернатив не может быть выбрана из-за отсутствия задержанных запросов, выполняются операторы в части else оператора select.

Семантика оператора select допускает возникновение других ситуаций. Например, в ситуации, когда имеется одна или несколько открытых альтернатив и при этом отсутствуют задержанные запросы, а оператор else отсутствует. В этом случае задача блокируется до возникновения обращения к одной из открытых альтернатив. Если ни одна из альтернатив не является открытой и часть else отсутствует, то система считает, что произошла взаимоблокировка, что приводит к возникновению в этой задаче исключительной ситуации. Эта исключительная ситуация может быть обработана локально внутри данной задачи либо передана в вызывающую задачу с одновременным удалением блокированной задачи.

[Возможны и другие формы оператора select. Оператор else может (1) отсутствовать или быть заменен одной или несколькими альтернативами задержки, или (2) одной альтернативой, вызывающей завершение работы задачи. Отметим также, что предложение else никогда не употребляется с условным оператором. Подробнее см. гл. 9 справочного руководства по языку Ада.]

Как показано на рис. 3.6, альтернатива accept может включать оператор с продолжением. Действия альтернативы accept могут быть разбиты на две части, которые выполняются последовательно:

  1. Часть рандеву, состоящая из оператора accept, выполняющегося в тот период времени, когда запрашивающая задача блокируется, и
  2. Оставшаяся часть, следующая за оператором accept, и выполняющаяся параллельно с вызывающей задачей после завершения процесса рандеву.

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

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

3.2.4. Задачи общего назначения

Более общей формой обслуживающей задачи является задача, в тело которой входят обращения к другим задачам. Таким образом, термин "задача общего назначения" означает объединение в теле этой задачи функций задачи-обслуживателя и задачи-запросчика. После получения какого-либо запроса на обслуживание задача-обслуживатель должна быть в состоянии назначить работу для других задач-обслуживателей. В языке Ада обращение одной задачи к другой разрешается в теле оператора accept или внутри его продолжения.

Хорошим примером задачи общего назначения является приведенная на рис. 2.5 задача Portfolio_Server. Обращение задачи Member для обновления портфеля к Portfolio_Server генерирует другое обращение - Portfolio_Server к Roster_Server. Последнее обращение осуществляется в целях проверки наличия у задачи Member привилегий на обновление портфеля (право на запись).

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

Разумеется, существует практический предел числа вызовов в нециклической цепочке вызовов. Этот предел является функцией обусловливаемого сцеплением программ отрицательного эффекта, оказываемого на производительность написанных на языке Ада программ. Мы проиллюстрировали природу этого отрицательного эффекта при рассмотрении улучшенной структуры "пакет-задача", приведенной на рис. 3.2.

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

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

Предположим, что обслуживающей задаче В необходимо оказать услугу, требующую длительных вычислений и приводящих в процессе рандеву к блокировке на длительное время задачи-запросчика А. Если в течение этого интервала времени задача А тем не менее в состоянии выполнять какую-то полезную работу, то, используя тактику проведения беседы между А и В, можно сократить время блокировки задачи А. Протокол одного из рандеву в этом случае будет иметь следующий вид:

Задача А достигает запланированной точки ожидания и принимает вызов от В, завершая второе рандеву простым копированием выходных результатов, переданных на вход А от В. Это освобождает задачу В от блокировки за минимальное время.

Такая тактика эффективна только в том случае, если сумма интервалов времени для двух рандеву (А->В, затем В->A) меньше времени, требующегося для одного длинного рандеву (А->В). На рис. 3.7 приведена схема программы на языке Ада для ответственной за беседу части задачи В.

accept Lengthy_service(
   param_1: in ...;
   param_2: in ...)
do
   local_var_1:=param_1;    -- Копирование значения, соответствующего
                            -- param_1 в данный контекст.
   local_var_2:=param_2;    -- То же для param_2.
end Lengthy_service;        -- Конец первого рандеву.
--
-- Операторы последовательности, осуществляющие длительную обработку над
-- значениями local_var_1 и local_var_2 для получения результата, необходимого
-- запросчику.
-- Предполагается, что результат локально сохраняется в переменной
-- wanted_result (желаемый результат),
--
Requestor_task_name.Lengthy_service_result(
   wanted_result);      -- Обращение назад к запросчику и передача требуемого
                        -- ему результата.
-- Конец операторов последовательности; конец второго рандеву.

Рис. 3.7. Часть протокола для задачи-обслуживателя, использующего
          двустороннюю коммуникацию для минимизации времени блокировки
          задачи-запросчика в течение рандеву.
назад оглавление вперед
Рейтинг ресурсов "Весь Екатеринбург"