Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Брой распознанный текст.doc
Скачиваний:
0
Добавлен:
01.04.2025
Размер:
1.18 Mб
Скачать

1.3.3. Языковые средства для параллельных ходов работы

Текстуальная нотация программ (в противоположность графическому представлению в виде блок-схем или сетям Петри) по существу является линейной. Несмотря на эту линейность нотации, она позволяет тексту­ально описывать параллельные процессы. Одну из таких возможностей предлагает уже применявшаяся нами в примерах парапельная компози­ция программ. Для программ Р1 и Р2 запись

(Г Р1 II Р2 jj

представляет программу, которая в качестве своего выполнения обладает параллельным ходом работы программы Р1 наряду с программой Р2. Для этого используется также синтаксис

parbegin PI par Р2 parend.

Поскольку параллельная композиция ассоциативна, часто пишут

itpjII -II p„il

вместо

[Гр,|| 1Гр2|| -fTPn-ill Pn-U - JJ JJ

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

.Наряду с этой структурированной (ориентированной на скобки) воз­можностью определения параллельного выполнения в некоторых языках программирования мы находим операторы, позволяющие динамически создавать семейство параллельных процессов. Для этого используется специальный оператор, который напоминает оператор вызова процеду­ры, но здесь при выполнении вызова не ожидают окончания выполнения процедуры. Более того, после начата выполнения процедуры параллель­но этому продолжается выполнение "главной программы". Мы говорим о расщеплении (англ. fork) потока управления.

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

Пример (образование параллельных процессов). Пусть р - описание программ!,! (или обозначение для такого описания), а х - идешификатор некоторого процесса (новый экземпляр выполнения программы). Опера­тор

start р паше х

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

terminate х

значения (rue указывает на то, что процесс, обозначенный через х, уже закончен. Пока процесс протекает, булевское выражение лает значение false, С помощью

terminate х

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

process х.

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

start ... name х.

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

Г process х ;

proc р =: ... ; {объяатение процедуры}

start р name х ; {начать параллельно протекающий процесс} if terminated х then {сбор, если процесс

не заканчивается}

else terminate х

f«; - J □

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

I

t I

обозначается соединение (слияние) этих потоков в один поток управле­ния. После слияния управляющий поток продолжается только в случае, когда ;пя обеих ветвей управляющий поток готов к продолжению. Эти

графические изображения могут быть обобщены на расщепления и слия­ния с п управляющими потоками.

В заключение рассмотрим один весьма исчерпывающий пример па­раллельного выполнения программ. Мы дадим вариации последовательно

и параллельно выполняющихся программ для решения одной из постав­ленных задач.

Пример (поиск в дереве). Пусть заданы тип node вершин и тип tree дво­ичных деревьев с помощью объяатения

sort tree = empty J cons(tree left, node root, tree right) .

Пусть задано двоичное дерево t, в котором надо проверить, встречается

ли определенная вершина i. В программе будем использовать процедуру suche с заголовком процедуры

proc suche = (tree t, node i, var bool z)

,которая результирующей переменной z присваивает значение true, если в двоичном дереве t имеется вершина, помеченная через i, и значение false в противном случае. Последовательная программа, которая решает поставленную задачу, выглядит следующим образом:

proc suche = (tree t, node i, var boo) z):

if -nsempty(t)

then if root(t) = i then z := true

else var bool zl, zr;

suche(left(t), i, zl); suche(right(t), i, zr); z := zl v zr

П

else z .= false fi.

Первая простая программа, которая работает с независимыми параллель­ными поисками, гласит:

proc suche = (tree t, node i, var bool z): if ~4.sempty(t) then if root(t) = i

then z := true

else var bool zl, zr := false, false;

ff suche(lefl(t), i, zl) || suche(right(t), i, zr) JJ; z := zl v zr

fi

elsez := false fi.

Вызов процедуры suche(t, i, z)

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

Впрочем, при таком предписании, как правило, будет дальше про­должаться параллельный поиск, лаже в том случае, когда уже будет уста­новлено, что результатом является true. Эту неэффективность можно уст­ранить путем использования программной структуры, в которой парал­лельные процессы поиска обмениваются сообщениями. Если применить общую переменную z, которой первоначально присваивается значение false, то получим следующую процедуру

:proc suche = (Iree t, node i, var bool z)

if Hscmpty(t)

then if root(t) = i

then await true then z := true endwait else var bool b;

await true then b := z endwait; if -b then IT suche(left(t), i, z) || suche(right(t), i, z) jj else nop

fi

fi

else nop fi.

С помощью объявления и последующего вызова

var bool z := false; suche(t, i, z)

порождается каскад параллельно выполняющихся программ. После того как все эти профаммы завершат работу, переменная z будет содержать искомый результат. Как только один из процессов найдет искомую вер­шин)1, другие процессы прекратят поиск. Однако можно обойтись и без обшей переменной, в которую записывают параллельно выполняющиеся программы. Это демонстрируется следующим предписанием, которое ра­ботает с образованием процессов:

proc suche = (tree t, node i, var bool z): if -"isempty(t) then if root(i)

then process pi, pr;

var bool zl, zr := false, false;

start sue he (left (i), i, zl) name pi;

start siiche(right(!), i, zr) name pr;

await zl v zr v (terminated pi л terminated pr)

then z :== zl v zr endwait; if --terminated pi then terminate pi fi; if --terminated pr then terminate pr fi else z := true fi

else z := false fi.

Поиск в дереве t вершины i ориентируется на структуру дерева. При этом порождается дерево процессов. Если процесс-потомок i находит искомуто вершину в корне своего дерева, то этот результат передается наверх.

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

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

Обратим внимание, что при обсуждении процессов мы отказались от количественных временных аспектов (как, например, "нужно ожидать 5 секунд"). Количественно критичные по времени процессы и, соответ­ственно, программы, которые описывают такие процессы, требуют соб­ственных концепций, которые, однако, родственны обсуждавшимся язы­ковым элементам. Такие программы используются в режиме реального времени для управления реальными процессами.