Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Языки программирования. Концепции и принципы
.pdf
Глава 6
Асинхронные процессы
6.1. Основные проблемы ............... 152
6.2. Семафоры Дейкстры .............. 155
6.3. Сигналы .................................. 157
6.4. Концепция внешней
дисциплины ................................... 159
6.5. Концепция внутренней
дисциплины: мониторы ................. 160
6.6. Рандеву................................... 164
6.7. Проблемы рандеву.................. 165
6.8. Асимметричное рандеву ......... 166
6.9. Управление асимметричным
рандеву (семантика
вспомогательных конструктов) ...... 167
6.10. Реализация семафоров,
сигналов и мониторов
посредством асимметричного
рандеву ......................................... 169
6.11. Управление асинхронными
процессами в Аде .......................... 172

152
Современное состояние языков программирования
6.1. Основные проблемы
До сих пор мы занимались последовательным программированием и соответству
ющими аспектами ЯП. Этот вид программирования характеризуется представле
нием о единственном исполнителе, поведение которого и нужно планировать. Во
всяком случае, это поведение всегда можно было мыслить как некоторый про
цесс – последовательность действий из репертуара этого исполнителя, строго
упорядоченных во времени.
Основной стимул для расширения сферы наших интересов состоит в том, что
реальный мир весьма разнообразен и сложен, так что его моделирование одним
последовательным процессом часто оказывается неадекватным.
Поэтому займемся языковыми средствами, предназначенными для планиро
вания поведения некоторого коллектива исполнителей. Мы употребляем слово
«коллектив», а не просто «совокупность» исполнителей, чтобы подчеркнуть, что
нас интересуют, как правило, только взаимодействующие исполнители, совмест
но решающие некоторую проблему. Поведение каждого из них будем попрежне
му мыслить как некоторый процесс, но будем считать, что поведение коллектива
в целом в общем случае никакой строго упорядоченной во времени последова
тельностью действий не описывается.
Однако оно естественным образом описывается совокупностью (коллекти
вом) взаимодействующих последовательных процессов. Так что соответствую
щие языковые средства должны предусматривать описание такого коллектива
процессов (в частности, их запуск и завершение) и (самое сложное, важное и ин
тересное) описание их взаимодействия. Подчеркнем, что если взаимодействие
процессов отсутствует, то коллектив распадается на отдельные процессы, задача
управления которыми решается в последовательном программировании.
Подчеркнем, что коллективы исполнителей сплошь и рядом встречаются в ре
альной жизни: сам компьютер естественно представлять коллективом исполните
лейкомпонент (центральный процессор, память, периферия), сети компьютеров –
также коллектив исполнителей; производственные, военные, информационные
процессы естественно группировать в коллективы. Наконец, в некоторых задачах
коллективы процессов возникают в целях оптимизации (матричные операции,
сеточные задачи и т. п.).
Как уже сказано, упорядоченность отдельных событий (фактов поведения)
в разных процессах часто можно считать несущественной для задачи, решаемой
всем коллективом. Поэтому обычно такой упорядоченностью можно пренебречь
и считать процессы асинхронными, в частности исполняемыми параллельно на
подходящем коллективе процессоров (реальных или виртуальных).
По этой причине соответствующее программирование называют «параллель
ным», а соответствующие ЯП – параллельными, или языками параллельного
программирования (ЯПП).
С другой стороны, когда упорядоченностью событий в отдельных процессах
пренебречь нельзя, программа должна ее явно или неявно предписывать, для чего

Асинхронные процессы
в ЯПП должны быть соответствующие средства синхронизации асинхронных
процессов. Важнейшая цель синхронизации – организация обмена информацией
между исполнителями (асинхронными процессами), без которого их взаимодей
ствие невозможно.
Итак, нас будут интересовать особенности ЯПП и прежде всего средства синх
ронизации и обмена как частные случаи средств взаимодействия процессов.
Основные проблемы (кроме обычных для последовательного программирова
ния) фактически названы. Это синхронизация, обмен (данными), запуск процес
сов, завершение процессов.
Наиболее интересные работы в области ЯПП прямо или косвенно имеют сле
дующую перспективную цель: выработать систему понятий, позволяющую кон
центрироваться на решении специфических проблем параллельного программи
рования тогда и там, когда и где это существенно для решаемой задачи, а не для
выбранных средств программирования. Другими словами, цель состоит в том,
чтобы программировать асинхронные процессы со степенью комфорта, не усту
пающей лучшим достижениям последовательного программирования.
Эта цель еще впереди, но есть ряд достижений, с которыми мы и познакомимся.
Наша ближайшая цель – продемонстрировать как пользу, так и проблемы па
раллельного программирования, а главное – серию языковых средств, применяе
мых для решения этих проблем: семафоры, сигналы, мониторы, рандеву, каналы,
а также связанные с ними языковые конструкты.
В качестве основного примера выберем характерную для параллельного про
граммирования задачу о поставщике и потребителе некоторой информации.
153
Задача о поставщике и потребителе
Итак, нужно описать два процессасоисполнителя, один из которых (поставщик)
вырабатывает некоторое сообщение и поставляет его потребителю, который,
в свою очередь, получает сообщение и потребляет его в соответствии с назначени
ем (которое нас не интересует).
Предполагается, что эти два исполнителя способны работать совершенно неза
висимо, кроме ситуаций, когда они непосредственно заняты обменом информаци
ей между собой.
Вопрос о том, что значит «вырабатывает» и «потребляет», нас также в этой зада
че не интересует, поскольку касается в каждом случае только одного из процессов и,
следовательно, укладывается в знакомые рамки последовательного программиро
вания. Зато вопрос о том, как понимать слова «поставляет» сообщение и «получа
ет» сообщение, касается самой сути взаимодействия наших соисполнителей.
Различным вариантам уточнения этих двух слов и будет посвящена серия на
ших попыток решить задачу. После некоторых колебаний выбран скорее истори
ческий, чем логический порядок изучения возможных подходов к ее решению.
Хотя, усвоив предыдущий материал, читатель подготовлен к восприятию совре
менных решений, полезно проникнуться проблемами первопроходцев, тем более
что рассматриваемые при этом средства так называемого «нулевого уровня» (сема
форы и сигналы) используются и в современных языках (например, в Смолтоке).

154
Современное состояние языков программирования
Необходимость уточнения смысла слов «поставляет» и «получает» вызвана
тем, что они подразумевают некоторый способ взаимодействия (в остальном со
вершенно независимо работающих) процессов.
Во всяком случае, это не может быть вызов обычных процедур «поставлять» и
«получать» в соответствующем процессе хотя бы потому, что выполнение таких
процедур (по самому их смыслу) предполагает определенное состояние готовнос
ти внешней для процесса среды. Если в этой среде нет ничего, кроме процесса
партнера (о состоянии которого в общем случае ничего не известно – ведь он
работает асинхронно), то взаимодействие просто невозможно.
Итак, следует сконструировать внешнюю среду, допускающую взаимодей
ствие асинхронных процессов.
Первое, что приходит в голову человеку, привыкшему к последовательному
программированию, – считать процессыисполнители работающими в общем
внешнем контексте (среде), где им доступны общие переменные. Рассмотрим со
ответствующие решения.
Итак, первый вариант взаимодействия – через общие переменные. Точнее го
воря, будем считать, что поставщик и потребитель программ обмениваются сооб
щениями через некоторый общий (доступный обоим партнерам) буфер, способ
ный хранить ограниченное число сообщений. Подробности устройства буфера
нас пока не интересуют. Однако ясно, что, если не принять дополнительных мер,
состояние буфера будет столь же непредсказуемо, как и состояние процессапарт
нера. Поэтому вся проблема – в том, как избежать этой непредсказуемости.
Представим некоторую начальную детализацию наших процессов и общего
контекста. Будем писать в уже опробованном «адовском» стиле.
package общий is
. . .
b : буфер;
procedure поставить(X : in сообщение);
procedure получить(X : out сообщение);
. . .
end общий;
with общий; use общий; with общий; use общий;
task поставщик is; task потребитель is;
. . . . . .
loop; loop;
. . . . . .
выработать(X); получить(X);
поставить(X); потребить(X);
. . . . . .
end loop; end loop;
end поставщик; end потребитель;
Если считать, что процедуры «поставить» и «получить» соответственно запол
няют и освобождают буфер b, то очевидно, что наша программа правильно рабо
тать не будет! Как уже сказано, состояние буфера непредсказуемо. В частности,

Асинхронные процессы
нельзя заносить сообщения в полный буфер и нельзя выбирать сообщения из пу
стого буфера. Поэтому нужны две функции «полон» и «пуст», сообщающие
о состоянии буфера.
Но теперь будет непредсказуемой связь между значением такой функции и
реальным состоянием буфера – ведь оно может изменяться сколь угодно быстро
изза асинхронных действий партнеров. Итак, нужна определенная синхрониза
ция действий партнеров.
Именно состояние буфера не должно изменяться другим партнером, пока пер
вый партнер узнает состояние буфера, принимает решение о посылке или выбор
ке сообщения и выполняет это решение.
Важно понимать, что такая синхронизация может быть выполнена только
с помощью средств, поставляемых внешним контекстом (внешней средой). На
пример, люди применяют для синхронизации с партнерами часы, видят партне
ров, чувствуют и т. п.
Эти внешние средства должны удовлетворять определенным требованиям,
чтобы обеспечивать корректность синхронизации. Во всяком случае, один парт
нер не должен иметь возможность мешать другому партнеру воспользоваться
средством синхронизации. Это важнейшее требование корректности можно обес
печивать поразному.
Основной принцип – монополизация доступа к соответствующему средству.
Другими словами, если процесс обладает правом пользоваться средством синхро
низации, то в период, когда он им реально пользуется, внешняя среда гарантирует
его монополию на это средство, точнее, на пользование этим средством в опреде
ленной роли (в этот же период тем же средством в другой роли может пользовать
ся партнер, как в случае рандеву и каналов, – см. ниже).
Принцип монопольного доступа можно интерпретировать и так, что весь акт
доступа считается неделимым, выполняемым мгновенно с точки зрения внутрен
него времени процессов, что не позволяет другому процессу вмешиваться в ис
полнение этого акта.
Одно из назначений синхронизации – обеспечить монопольный доступ к об
щим переменным. Как видим, это возможно лишь за счет монопольного доступа
к базовым средствам синхронизации.
155
6.2. Семафоры Дейкстры
Рассмотрим два вида средств синхронизации – (двоичные) семафоры и (двоич
ные) сигналы.
Их основное назначение отражено в названии: семафоры применяют для синх
ронизации прохождений процессами своих так называемых «критических участ
ков» – вполне аналогично тому, как синхронизируют движение поездов, подни
мая и опуская семафоры и обозначая тем самым занятость железнодорожного
перегона.
Поезд допускается на перегон, если путь свободен (семафор поднят (открыт)),
поезд ждет своей очереди, если путь занят (семафор опущен (закрыт)), и, нако

156
Современное состояние языков программирования
нец, семафор закрывается, как только поезд выходит на перегон (занимает путь);
семафор открывается, когда поезд покидает перегон (освобождает путь).
Внешняя среда, обеспечивающая управление процессами и семафорами, дол
жна обеспечивать, в частности, приостановку и активизацию процессов, органи
зацию их очереди к семафору и, конечно, монопольный доступ к семафорам (не
делимость действий с семафорами).
Все эти возможности Дейкстра предложил концентрировать в двух операциях:
оградить (S) и освободить (S)
для объекта S типа «семафор», принимающего два значения («свободен» и «занят»).
Семантика этих операций такова:
Оградить(S): if S=свободен then S:=занят else [приостановить текущий процесс и
поставить его в очередь(S)].
Освободить(S): if пуста(очередь(S)) then S:=свободен else [возобновить процесс,
первый в очереди(S)].
Семантика семафоров приспособлена к такой дисциплине программирования,
когда «критический участок» любого процесса предваряется операцией «огра
дить» и завершается операцией «освободить». Процесс, желающий монопольно
распоряжаться некоторым ресурсом, охраняемым семафором S, первой операци
ей «закрывает за собой дверь» и не дает другим процессам себе мешать. Завершив
свои дела, он второй операцией «открывает дверь» для других желающих.
Тем самым по отношению к общим (разделяемым) ресурсам реализуется ре
жим взаимного исключения с развязкой – на своих критических участках про
цессы взаимно исключают доступ партнеров к ресурсу, а на остальных участках
совершенно развязаны – никак не ограничивают действий партнеров. Понятно,
что разделяемых ресурсов может быть много. Тогда для каждого из них следует
заводить свой семафор.
В нашем случае такой ресурс один – буфер. Поэтому достаточно одного сема
фора (критические участки выделены):
package общий is
. . .
b : буфер; – буфер сообщений.
S : семафор; – с ним связаны соответствующие операции
procedure поставить...;
procedure получить...;
end общий;
with общий; use общий; with общий; use общий;
task поставщик is; task потребитель is;
. . . . . .
loop; loop;
выработать(X); оградить(S);
оградить(S); получить(X);
поставить(Х); освободить(S);
освободить(S); потребить(X);
end loop; end loop;
end поставщик; end потребитель;

Асинхронные процессы
157
Теперь партнеры не мешают друг другу в период доступа к буферу и вежливо
уступают доступ, когда он им не нужен.
Однако программа все равно не будет работать корректно!
(Почему?)
Дело в том, что партнеры не следят за состоянием буфера. Не следят сами и не
помогают следить напарнику.
Можно было бы воспользоваться функциями «полон» и «пуст», сообщающи
ми о состоянии буфера. Например, так (пишем только внутренние циклы):
loop; loop;
выработать(X); оградить(S);
оградить(S); while пуст loop
while полон loop ждать;
ждать; – фикс. время end loop;
end loop; получить(Х);
поставить(Х); освободить(S);
освободить(S); потребить(Х);
end loop; end loop;
Однако и такое решение неприемлемо и работать не будет. (Почему?)
Вложенные циклы с ожиданием могут долго работать... в монопольном режи
ме! Например, если буфер полон, то бессмысленно ждать внутри критического
участка, пока он освободится, – ведь партнеру буфер недоступен, так как семафор
закрыт! Это пример тупика.
Следует писать так:
while полон loop while ïóñò loop
освободить(S); освободить(S);
ждать; ждать;
оградить(S); оградить(S);
end loop; end loop;
Теперь программа будет работать. (Хорошо ли?)
Не слишком хорошо. Внутренние циклы нерационально расходуют активное
время процессора – это особенно неприятно при реализации всей системы на
единственном физическом процессоре. Циклов активного ожидания в таком слу
чае стараются избегать.
6.3. Сигналы
Избежать циклов активного ожидания можно, заменив его пассивным ожидани
ем (без занятия процессора), организуемым с помощью средств синхронизации,
называемых сигналами.
Сигнал Е – это объект типа «сигнал», принимающий значения «есть» и «нет».
Аналогично семафору с ним связаны очередь процессов, «ждущих сигнала Е»,
и две операции: послать (Е) и ждать (Е), – управляющие этой очередью. Семанти
ка этих операций такова:
послать(Е):
if пуста(очередь (Е)) then Е:=есть;

158
else [возобновить первый ("ждущий") процесс в очереди(Е)];
ждать (Е):
if Е=есть then Е:=нет;
else [приостановить текущий процесс и поместить его (последним) в очередь (Е)].
Современное состояние языков программирования
Как видим, семантика сигналов двойственна семантике семафоров (послать =
освободить, а ждать = оградить).
Однако если семафорами пользуются для того, чтобы в рамках одного процес
са ограждать критические участки от возможного влияния других процессов, то
сигналы используются именно для организации взаимного влияния процессов.
С помощью сигналов партнеры могут сообщать информацию о событиях, стано
вящихся им известными. С другой стороны, они могут пассивно (в очереди)
ждать наступления этих событий.
В нашем примере таких событий два: неполнота и непустота буфера. Поэтому
нужны два сигнала – непуст и неполон. Заметив, что цикл ожидания становится
ненужным (ожидание обеспечивают средства синхронизации – за это и боролись!),
напишем (теперь уже окончательную) схему нашей программы полностью:
package общий is
. . .
b: буфер; – для сообщений
s: семафор;
неполон, непуст: сигнал; – сигналы-будильники
procedure поставить(X: in сообщение);
procedure получить(X: out сообщение);
function полон ...;
function пуст...;
end общий;
task поставщик is task потребитель is
X: сообщение; X: сообщение;
. . . . . .
loop; loop;
выработать(X); оградить(S);
оградить(S); if ïóñò then
if полон then освободить(S);
освободить(S); ждать(непуст);
ждать(неполон); оградить(S);
оградить(S); end if;
end if; получить(Х);
поставить(Х); освободить(S);
освободить(S); потребить(Х);
послать(непуст); послать(неполон);
end loop; end loop;
. . . . . .
end поставщик; end потребитель;

Асинхронные процессы
Полезно подчеркнуть следующие существенные моменты.
1. Условный оператор развязывает действия партнеров. Без него была бы
фактически полная синхронизация (то есть процессы не были ли бы факти
чески асинхронными).
2. Ограждение проверки нужно для монополизации разделяемого ресурса,
а освобождение – чтобы избежать тупика. Ведь ожидаемый сигнал может
прийти только от партнера – надо дать последнему возможность работать
с буфером. При этом первый же цикл партнера обязательно даст такой сиг
нал, так что «застрять» на ожидании нельзя.
3. Семафор обеспечивает поочередную работу процессов – он не может быст
ро мигать в результате работы только одного процесса, когда второй ждет
у семафора. При первом же освобождении пойдет второй процесс, и первый
будет ждать у своего «оградить», если первым до него доберется.
4. Целостность объектов в нашей программе не обеспечена – связь семафора
с буфером, а также сигнала с буфером никак в программе не отражена –
отражена лишь в мыслях программиста. Это неадекватно (не отражает сути
дела) и опасно, так как нет контроля за этими связями.
Иными словами, свойства семафоров и сигналов как языковых конструк
тов не соответствуют основному критерию качества ЯП (усложняют про
граммирование и понимание программ) .
5. Структурно не отделены части программы, существенно зависящие от вза
имодействия с другими процессами, от частей, в которых можно абстраги
роваться от такого взаимодействия (и самого факта управления одним из
членов коллектива асинхронных процессов). Средства программирования
таковы, что при написании буквально любой команды следует опасаться
«подводных камней» параллелизма.
159
6.4. Концепция внешней дисциплины
Частично резюмируя пп. 4 и 5 из параграфа 6.3, частично обобщая их, можно сде
лать вывод об использовании в нашей программе (сознательно или интуитивно)
так называемой «концепции внешней дисциплины» взаимодействия процессов.
Термин «внешней» отражает отношение дисциплины к разделяемым процессами
ресурсам.
Суть этой концепции – в том, что о дисциплине (правилах) использования раз
деляемых ресурсов должны заботиться сами процессыпартнеры. Другими слова
ми, она «локализована» вне общих ресурсов. Примером такой дисциплины может
служить «скобочное» правило применения операций «оградить» и «освободить».
Итак, мы рассмотрели пример задачи поставщик–потребитель и показали, как
в рамках концепции внешней дисциплины при обмене справиться с проблемой
порчи данных (немонопольный, множественный доступ), а при синхронизации –
с проблемой порчи управления (тупики и лишние ожидания). При этом мы совер
шенно игнорируем запуск и завершение процессов.

160
Полезно понимать, что взаимно дополнительные свойства семафоров и сигналов
позволяют сводить использование семафоров к использованию сигналов, и наобо
рот. Однако если при этом применение сигналов вместо семафоров допускает тол
кование, вполне согласующееся с названием соответствующих операций, то обрат
ное неверно. Именно парноскобочное применение операции оградить (S) ...
освободить (S) естественно трактовать как
ждать (разрешенияввестивкритическийучастокдляS) и
послать (сигналозавершениикритическогоучасткадляS),
где в скобках указаны два сигнала, соответственно посылаемые и ожидаемые внеш
ней (управляющей процессами) средой (точнее, некоторым процессомдиспетче
ром), в которой находится пара операторов
послать (сигналозавершениикритическогоучасткадляS) и
ждать (разрешенияввестивкритическийучастокдляS).
Так что один семафор сводится к двум сигналам. Свести к одному опасно! Иначе
диспетчер будет равноправен с другими процессами. Здесь же только он имеет пра
во послать (разрешение...).
Невозможность обратной замены (сигналов на семафоры) с сохранением
«скобочного» смысла операции очевидна – ведь в процессе может оказаться толь
ко одна из таких операций. Однако если не сохранять симметрию операций в од
ном процессе, то и здесь заменяющее истолкование возможно: «ждать» трактует
ся как «оградить» последующие операторы, пока не будет сигнала (но не от
вмешательства, а от исполнения), а «послать» – как «освободить» от вынужден
ного ожидания.
Современное состояние языков программирования
6.5. Концепция внутренней
дисциплины: мониторы
Замысел внутренней дисциплины вполне укладывается в идеологию РОРИУС:
разделяемый ресурс следует представить некоторым специальным комплексом
услуг, реализация которого концентрирует в себе все особенности параллелизма
и конкретной операционной среды, а использование становится формально со
вершенно независимым от поведения и даже наличия процессовпартнеров.
Здесь слово «формально» подчеркивает факт, что в процессепользователе
оказывается невозможным обнаружить какиелибо следы присутствия процес
совпартнеров. Более того, его семантика полностью описывается в терминах вза
имодействия с одним только указанным специальным комплексом услуг. Хотя,
конечно, эти услуги содержательно связаны с наличием и функционированием
процессовпартнеров.
Итак, концепция внутренней дисциплины состоит в локализации всех средств
управления взаимодействием коллектива процессов в рамках некоторого явно
выделенного разделяемого ресурса – специального комплекса услуг, называемого
монитором.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
