Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Языки программирования. Концепции и принципы

.pdf
Скачиваний:
2
Добавлен:
08.09.2026
Размер:
2 Мб
Скачать
☆
Асинхронные процессы
161
Мониторы могут быть весьма разнообразными. Содержательно близкие роли
играют диспетчеры (супервизоры) операционных систем, но их далеко не всегда сознательно проектируют в рамках концепции внутренней дисциплины. Мы рас смотрим некоторую полезную абстракцию, так называемые мониторы Хансена Хоара, предложенные в 1973–1975 гг.
Продолжим рассматривать нашу задачупример о поставщике и потребителе. Ключевая идея: разделяемый ресурс (буфер) следует превратить в комплекс
услуг, самостоятельно обеспечивающий корректность доступа к буферу. С по добной идеей мы уже знакомились в последовательном программировании – па кеты предоставляют комплекс услуг с защитой внутренних ресурсов от нежела тельного доступа.
Теперь требуется аналог пакета в ЯПП. Пакет был в состоянии защитить ре
сурс за счет того, что доступ допускался только через разрешенные операции. Причем поскольку исполняемый процесс был единственным, всегда исполнялась единственная операция пакета. Если это фундаментальное свойство пакета со хранить, то для корректности доступа к ресурсу совершенно несущественно, сколько и каких операций выполняется вне пакета.
Таким образом, пакет с указанным встроенным фундаментальным свойством
операций (встроенным по семантике соответствующего ЯПП) пригоден и для об служивания асинхронных процессовпользователей. Такой пакет и называется монитором ХансенаХоара. (Он был воплощен, в частности, в ЯП Параллельный Паскаль Бринча Хансена.)
Другими словами, монитор – это пакет со встроенным режимом взаимного
исключения предоставляемых пользователю операций. Тем самым реализуется монопольный доступ процесса к ресурсу без какихлибо специальных указаний со стороны этого процесса (и усилий программиста).
Полезно осознать, что здесь мы в очередной раз имеем дело с рациональной
абстракцией и удобной конкретизацией. Монитор позволяет пользователю от влечься от «параллельной природы» использования ресурса и работать с ним в обычном последовательном (монопольном) режиме. Вместе с тем он позволяет программисту при создании тела монитора полностью учесть «природу» конкрет ного ресурса и доступа к нему.
Сказанное следует понимать именно так, что пользователь может программи
ровать, полностью игнорируя параллелизм процессов. Этот идеал пользования разделяемым ресурсомбуфером в случае нашего примера выглядит так:
with áóô; use áóô; with буф; use буф; task поставщик is task потребитель is
. . . . . .
loop loop
выработать(X); получить(X); поставить(X); потребить(X);
end loop; end loop;
end поставщик; end потребитель;
Здесь «буф» обозначает нужный контекст. Им и служит монитор, предостав
ляющий необходимые услуги. Достигается полная абстракция от способа обмена.
162
Современное состояние языков программирования
Обратите внимание, наш идеал в точности совпадает с первоначальным замыс лом, прямое воплощение которого было неработоспособно! Такой возврат свиде тельствует об очевидном прогрессе – в программе пользователя нет ничего лиш него!
Перейдем к реализации монитора:
with общий; use общий; – чтобы не переписывать monitor буф is – !! в Аде такого нет, продолжаем писать на Ада-подобном ЯП
entry поставить(X : in сообщение);
entry получить(X : out сообщение); end буф; – ключевые слова "monitor" и "entry" подчеркивают особую семантику процедур "поставить" и "получить", то есть режим взаимного исключения
monitor body áóô is
procedure поставить(X: in сообщение) is
begin
if полон then ждать(неполон) end if; занести(Х); – "обычная" запись в буфер
послать(непуст); – сигнал для "получить" end поставить; procedure получить(X: out сообщение) is begin
if пуст then ждать(непуст) end if;
выбрать(Х); – "обычная" выборка из буфера
послать(неполон); – сигнал для "поставить" end получить;
end áóô;
Чтобы все встало на свои места, в пакете «общий» нужно названия процедур
«поставить» и «получить» заменить на «занести» и «выбрать» соответственно. Процедуры, объявленные в мониторе («мониторные», или «монопольные», про цедуры), пользуются «обычными» пакетными процедурами, в которых можно конкретизировать такие особенности буфера, как его организация массивом или списком, очередность выборки сообщений и т. п., не относящиеся к параллелизму.
Важно понимать, что сам по себе режим взаимного исключения не спасает от
тупиков. Ведь, например, при переполнении буфера процедура «поставить» не может нормально завершить свою работу, и если не внести уточнений в семанти ку монитора, то нельзя избежать тупика (пока не завершена процедура «поста вить», не может работать «получить», чтобы освободить буфер!).
Поэтому на самом деле мониторные процедуры могут быть приостановлены
в указанных программистом местах, с тем чтобы дать возможность запускать дру гие процедуры. Для этого можно воспользоваться аппаратом сигналов, что и сде лано в нашем примере. Так что, например, первая процедура может приоста новиться на операторе «ждать(неполон)», и так как в мониторе не остается активных процедур, он готов при необходимости активизировать вторую про цедуру (которая в этом случае наверняка пошлет сигнал «неполон»), а после за вершения второй процедуры сможет продолжить свою работу первая.
Асинхронные процессы
163
Итак, в семантику сигналов также внесена «мониторная» коррекция: можно возобновлять процесс из очереди только при условии, что он не приостановлен на процедуре из активного монитора.
Полезно подчеркнуть аналогию между взаимодействием мониторных процедур и сопрограмм. Напомним, что такое процессысопрограммы X и Y:
process X; process Y;
... ...
resume Y; resume X;
... ...
resume Y; resume X;
... ...
detach; detach; => главная программа
Процессысопрограммы исполняются на одном процессоре. Оператор resume при останавливает исполнение сопрограммы, в которой находится, и возобновляет исполнение указанной в нем сопрограммы с того места, на котором она была ранее приостановлена (или с самого начала, если это первое обращение к ней). Оператор detach возвращает управление главной программе. Как и в случае сопрограмм, для каждого монитора предполагается единственный исполнитель, способный приостанавливать исполнение мониторных процедур (на операторе «ждать») и возобновлять их исполнение с прерванного места. Однако, в отличие от сопрограмм, приостановка не означает немедленного возобновления некоторой фиксированной «сопрограммы», указываемой оператором приостанов ки, – этому исполнителю приходится ждать явной активизации мониторной про цедуры или появления нужного сигнала.
Сделаем выводы.
1. Мониторы обеспечивают высший уровень рациональной абстракции от особенностей параллелизма и удобные средства конкретизации, то есть это отличное средство структуризации программ.
2. Мониторы обеспечивают надежность предоставляемых процессампользо вателям услуг – пользователь не в силах «сломать» хорошо спроектирован ный и отлаженный монитор.
3. Мониторы обеспечивают ясность программирования процессов пользова телей. Достаточно сравнить наш «идеал», в котором нет ничего лишнего, и решение с помощью семафоров.
4. Мониторы обеспечивают эффективность, когда их используют вместе со средствами пассивного ожидания (например, сигналами).
5. Режим взаимного исключения в мониторе встроен, синхронизация обеспе чивается частично этим режимом, а в основном – посредством сигналов, развязка (независимость процессов) – асинхронностью процессовпользо вателей и правилами приостановки мониторных процедур.
Итак, мониторы многим хороши, но:
• представляя собой средство высокого уровня (абстракции), требуют низко уровневых средств для своей реализации (сигналов), то есть не могут слу жить единой концептуальной основой ЯПП;
164
Современное состояние языков программирования
• провоцируют создание административной иерархии программ там, где, по
сути, достаточно их непосредственного (горизонтального) взаимодей ствия. В результате такую систему трудно перестраивать, если на один мо нитор приходится много процессовклиентов (а если мало, то оказывается относительно много мониторовпосредников).
Это, кстати, встроенные недостатки любой административной системы.
Имеются и другие причины (в частности, невозможность единой стратегии
исполнения мониторных процедур, гарантирующей от тупиков), стимулирующие поиск иных языковых средств. Мы выделим в качестве важнейших две:
1) поиск единой концептуальной основы параллелизма, обеспечивающей при
емлемые средства абстракцииконкретизации без обязательных дополни тельных средств низкого уровня;
2) поиск выразительных средств, обеспечивающих «демократическое» взаи
модействие процессов без лишних посредников.
Другими словами, требуется единая основа параллелизма, обладающая доста
точно высоким уровнем, чтобы обеспечить ясность и надежность пользовательских процессов, и вместе с тем достаточно низким уровнем, чтобы было легко запрог раммировать и семафоры, и сигналы, и мониторы, и другие полезные примитивы.
6.6. Рандеву
Основная идея (Хоар, Хансен – 1978 г.): соединить синхронизацию и обмен в одном примитиве, моделирующем встречу (свидание, рандеву) процессовпартнеров.
Например, для передачи данных из переменной X процесса А в переменную Y
процесса В следует написать:
task A is task B is
X : сообщение; Y : сообщение;
... ...
В ! X; -- заказ рандеву A ! Y; -- заказ рандеву
-- с процессом В для передачи -- с процессом A для приема
-- из переменной X -- в переменную Y
... ...
end À; end B;
Семантику рандеву опишем на псевдокоде так:
if непуста (очередь партнеров) then
[выбрать партнера из очереди; выполнить присваивание Y:=X; активизировать партнера];
else [приостановить текущий процесс и поместить его в очередь партнеров по рандеву к процессу, с которым заказывается рандеву];
Итак, процесс А, желающий передать сообщение процессу В (своему партне
ру), должен «заказать» с ним рандеву посредством конструкта В!Х. Чтобы пере дача состоялась, процесс В должен также заказать рандеву посредством двой ственного конструкта A?Y.
Асинхронные процессы
165
Внешняя среда принимает эти заказы и приостанавливает партнера, первым подавшего заказ (первым пришедшего на рандеву) до подачи заказа вторым парт нером (то есть до его «прибытия» на рандеву). Когда оба партнера готовы, ранде ву происходит «мгновенно» (с точки зрения партнеров, «счастливые часов не наблюдают»).
Последнее сказано, скорее, для красного словца. Лучше было бы сказать, что ранде ву начинается и заканчивается для партнеров одновременно (симметрично).
Итак, в рандеву соединены синхронизация (взаимное ожидание) и обмен (присваивание). В рандеву воплощена вполне «демократическая» идея «горизон тальных», прямых связей между партнерами. В результате общие переменные не нужны. Не нужен и режим взаимного исключения при доступе к ним. Однако все это только в случае единичного обмена.
При регулярном обмене, как в задаче «поставщик–потребитель», требуется еще и развязка партнеров, которую рандеву само по себе не обеспечивает – ведь темп обмена ограничен возможностями медленного партнера.
Эта проблема решается за счет моделирования посредством рандеву активно го разделяемого ресурса – аналога пассивного буферамонитора. Здесь суще ственно используется относительно низкий уровень такого примитива, как ран деву, – с его помощью легко моделировать нужные конструкты. Правда, для этого требуются процессыпосредники – и это основной недостаток рандеву как примитива.
Итак, наш новый (активный) буфер будет процессомпосредником, взаимо действующим посредством рандеву и с поставщиком, и с потребителем. Назовем этот процесс «буф»:
task Ïîñò is task Ïîòð is
... ...
áóô ! X; áóô ? Y;
... ...
end поставщик; end потребитель;
Полезно обратить внимание на эту конфигурацию. Она может служить источником новых идей (см. ниже о каналах).
task áóô is
...
Ïîñò ? Z;
...
Ïîòð ! Z;
... end áóô;
6.7. Проблемы рандеву
Итак:
а. Рандеву требует дополнительных процессов – ведь оно по замыслу связы
вает только активные объекты (при их взаимном «согласии»).
166
б. Достаточно взглянуть на наш «буф», чтобы понять, что без специальных
средств отбора возможных рандеву невозможна развязка. Другими слова ми, нельзя менять темп рандеву с поставщиком относительно темпа ранде ву с потребителем – нужно учитывать, к какому именно виду рандеву (из двух возможных) готовы потенциальные партнеры. Поскольку о такой готовности знает только операционная (внешняя) среда, она и должна доставлять соответствующие средства отбора готовых к ран деву партнеров. Примеры таких средств рассмотрим позже (это, например, оператор select в Аде, alt в Оккаме). Раньше проблемы отбора не возникало потому, что буфер был пассив ным, – формально его готовность к работе не требовалась.
в. Партнеры должны называть друг друга по именам (должны «знать» друг
друга). Это неудобно, если роль одного из партнеров – обслуживать произ вольных клиентов. Примером может служить библиотечный пакет, которо му вовсе не обязательно «знать» имена пользующихся его услугами процес сов. По этому принципу устроен любой общедоступный сервис. Конечно, имя партнера может быть параметром обслуживающего процесса (назовем его для краткости «мастером», в отличие от обслуживаемых про цессов – «клиентов»). Но в таком случае возникают неприятные вопросы о способе передачи значения такого параметра. Статическая передача име ни клиента (в период трансляции) не дает возможности менять клиентов в динамике. А динамическая передача требует либо рандеву с «неизвест ным» клиентом, что невозможно, либо дополнительных «настраивающих» процессов, которым имена клиентов становятся известны не в результате рандеву.
Таким образом, практически невозможно создание библиотечных мастеров
посредством симметричного рандеву. Итак, с одной стороны, доказана очередная
неформальная теорема (о симметричном рандеву). С другой – именно она выну
дила Бринча Хансена при разработке средств параллелизма в Аде отказаться от
симметричного рандеву и ввести асимметричное, чтобы удовлетворить критич
ную для Ады потребность в библиотечных «мастерах».
Легко понять, почему для Ады проблема библиотечных мастеров критична – ведь это базовый ЯП для систем реального времени (то есть прежде всего для со здания библиотечных пакетов, обслуживающих потребности программ реального времени). Важно и то, что асимметричное рандеву лучше согласуется с адовской концепцией строгого контроля типов.
Современное состояние языков программирования
6.8. Асимметричное рандеву
Основная идея: «сервис вместо свидания» – сохранив партнерство взаимодей ствующих процессов (оба партнера должны быть готовы взаимодействовать), свести собственно взаимодействие к исполнению некоторого аналога процеду ры, определяемой только в одном из партнеров (обслуживающем партнере, «мас тере»).
Асинхронные процессы
Иногда говорят «процессслуга» и «процессхозяин». Однако так менее выра
зительно. Ведь слуга знает хозяина (если только это не «слуга народа»). А здесь идея именно в том, чтобы обслуживающий процесс был пригоден и для аноним ного клиента. Поэтому мы и предпочитаем термины «клиент» и «мастер».
Точнее говоря, для определенного вида взаимодействия (вида рандеву) выде
ляется процессмастер, предоставляющий услуги этого вида при соответствую щем рандеву. Остальные процессы по отношению к этому виду услуг (рандеву) считаются клиентами, получающими услуги в моменты рандеву этого вида с соот ветствующим мастером.
При этом для клиента предоставление ему услуги неотличимо от вызова им
подходящей процедуры. Другими словами, он совершенно «не замечает» асинх ронного характера своего взаимодействия с мастером. Все особенности паралле лизма (реального или виртуального) сказываются формально только на мастере. Это проявляется, в частности, в том, что в мастере предоставление услугиранде ву оформляется специальными операторами (так называемыми операторами приема входа «accept» и др.).
Итак, при переходе к асимметричному рандеву: а) можно написать библиотечного мастера; б) в отличие от симметричного рандеву, проще реализовать произвольные ус
луги, а не только передачу значений переменных
удобно разбивать между партнерами, обменивающимися значениями перемен
ных, но вполне удобно запрограммировать аналогично некоторой процедуре)
в) требуется, как и для симметричного рандеву, специальный аппарат управ
ления (аналогично аппарату, обслуживающему ранее рассмотренные при
митивы); в Аде это операторы accept, select и объявление входа (содержа
тельно это объявление вида рандеву).
(произвольную услугу не
167
;
6.9. Управление асимметричным рандеву (семантика вспомогательных конструктов)
Объявление входа в Аде имеет вид заголовка процедуры, перед которым стоит ключевое слово entry. Например:
task семафор is
entry оградить; -- параметры не нужны (почему?) entry освободить;
end семафор;
Здесь записана спецификация задачи (в Аде), моделирующей семафор. Други
ми словами, она описывает процессмастер, предоставляющий такие услугиран деву, которые содержательно позволяют ему выступать в роли семафора (если снабдить его соответствующим телом (см. ниже)).
С точки зрения процессаклиента, к входу мастера можно обращаться как
к процедуре. Например:
168
... оградить; ... освободить; ...
Современное состояние языков программирования
Однако имеется существенное отличие от обычных процедур – процедуры входы одного мастера работают в режиме взаимного исключения (это – следствие семантики рандеву), в то время как в общем случае в Аде процедуры считаются повторновходимыми, а процедуры одного пакета могут активизироваться асинх ронно (в том числе одновременно) из разных процессов.
Рассмотрим теперь оператор приема входа. Мастер считается готовым к ран деву, когда управление в нем достигает специального оператора «приема входа» вида
accept < заголовок_процедуры >
[ do < операторы > end ];
Заголовок_процедуры здесь совпадает с написанным после соответствующего entry в объявлении входа.
Когда и партнерклиент готов (то есть его управление достигает оператора вызова соответствующей процедурывхода), то требуемая синхронизация счита ется достигнутой, и происходит рандеву. Оно состоит в том, что после подстанов ки аргументов вызова исполняются операторы между do и end (если они есть). После этого рандеву считается состоявшимся, и партнеры вновь продолжают ра ботать асинхронно.
Оператор отбора входов (в Аде это оператор select) необходим, как уже гово рилось, для обеспечения развязки, чтобы рандеву разных видов не были жестко зависимы друг от друга. Его главное назначение – учет готовности клиентов к рандеву (и других условий), с тем чтобы не ждать рандеву с теми клиентами, которые «опаздывают» (не готовы к рандеву).
Общий вид этого оператора:
select
[ when условие ==> ] отбираемая_альтернатива
последовательность_операторов or ... or [when условие == > ] отбираемая_альтернатива
последовательность_операторов [ else последовательность_операторов ]
end select;
Отбираемой альтернативой может быть оператор приема, оператор задержки
или оператор завершения задачи. Когда управление в задаче достигает оператора отбора, то, вопервых, вычисляются все условия. Те альтернативы, для которых условие оказалось истинным, считаются «открытыми». Затем среди открытых альтернатив рассматриваются операторы приема, для которых очередь вызовов
Асинхронные процессы
соответствующих входов непуста. Если такие найдутся, то произвольным (с точ ки зрения программиста, но не создателя Адатранслятора) образом выбирается одна из таких альтернатив и происходит соответствующее рандеву. Затем выпол няется последовательность операторов, расположенная за этой отобранной аль тернативой, и оператор отбора считается выполненным.
Если среди открытых альтернатив не оказалось операторов приема, готовых к рандеву, то выполняется оператор задержки (delay) на указанное количество се кунд (если за это время возникает готовность к рандеву у открытых операторов приема, то отбирается альтернатива, готовая к рандеву, и оператор отбора завер шается, как обычно). После задержки и выполнения соответствующей выбранной альтернативы (accept или delay) последовательности операторов оператор отбора входов считается выполненным.
Если одной из открытых альтернатив оказался оператор завершения (termina te), то (если нет готовых к рандеву операторов приема) при определенных допол нительных условиях задача может быть завершена (до этого должны, в частности, завершиться запущенные нашей задачей подчиненные задачи).
Альтернатива «иначе» (else) может быть выбрана, если нет открытых операто ров приема, готовых к рандеву. Если в else стоит задержка, то во время этой задерж ки альтернативы уже не проверяются.
В одном операторе отбора, кроме операторов приема (хотя бы один оператор приема обязателен), допустимы либо только задержки, либо только завершение, либо только альтернатива «иначе».
В Аде имеются и другие разновидности оператора select, позволяющие не только мастеру не ждать не готового к рандеву клиента, но и клиенту не попадать в очередь к не готовому его обслужить мастеру.
169
6.10. Реализация семафоров, сигналов и мониторов посредством асимметричного рандеву
Продемонстрируем применение описанных средств управления рандеву на при мерах моделирования посредством рандеву рассмотренных ранее примитивов.
Семафоры (спецификацию задачи «семафор» см. выше на стр. 167).
task body семафор is begin
loop
accept оградить; – только синхронизация accept освободить; – без обмена – нет части «do»
end loop; end семафор;
Видно, что из всех богатых возможностей рандеву используется только синх ронизация – нет параметров и тела оператора приема. К тому же подчеркнута по
170
Современное состояние языков программирования
следовательность операций «оградить – освободить», невозможность нарушить
их порядок.
Сигналы
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 сигнал;
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]