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

1.3.2. Общие программные переменные

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

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

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

Никаких конфликтов не возникает, если параллельно выполняемые программы используют исключительно различные переменные пли по меньшей мере удовлетворяют условию Бернштейна, согласно которому программная переменная, которая в одной из парахтельно выполняемых программ встречается в левой части оператора присваивания, не встре­чается в других параллельно выполняемых программах. Впрочем, про­граммы, которые удовлетворяют этому условию и не используют опера­торы посылки/приема, не могут обмениваться сообщениями. Чтобы сде­лать возможным доступ к общим переменным из параллельно выпол­няющихся программ, но при этом предотвратить одновременные доступы к оAitoi'i и той же переменной, а эти доступы соответствующим образом координировать, используются охраняемые критические области.

Пусть Е - булевское выражение, a S - оператор или последователь­ность операторов. Если S может содержать чреватые конфликтами дейст­вия, то будем писать охраняемую критическую область в виде

await Е then S endwait чтобы выразить, что оператор S должен выполняться только при условии Е и при взаимном исключении других охраняемых критических областей. Условие Е назовем стражем (англ. guard), а оператор S - критической об­ластью.

Упомянутая охрагаемая критическая область выполняется следую­щим образом. Осуществляется ожидание до тех пор, пока не будет дос­тигнуто состояние, в котором выражение Е дает значение true и никакие другие параллельно протекающие процессы не выполняют как раз кри­тическую область (взаимное исключение). Выполнение критической об­ласти состоит тогда в выполнении оператора S, причем параллельно вы­полняющиеся программы при их вступлении в критическую область должны ожидать завершения выполнения оператора S. Если состояние, в котором Е дает значение true, никогда не наступает, то выполнение ох­раняемой критической области не завершается, т. е. возникает локаль­ный тупик. Таким образом, действия по выполнению охраняемой крити­ческой области соответствуют в ходе выполнения программы событиям, которые никогда не являются параллельными. Обеспечение взаимных исключений требует при выполнении программы наличия вышестоящего координирующего процесса.

Пример (параллельное вычисление факториала). Следующая программа вычисляет а! с помощью двух параллельных программ с общими пере­менными taken и z. При этом переменная z служит для обмена сообще­ниями, а переменная taken является вспомогательным средством для ко­ординации обменов сообщениями.

var bool taken, var nat у, var nat x := true, 1, n;

while n > 0 do await taken then taken, z := false, n endwait;

n := n— 1

od

II

while x > 1 do await --taken then taken, x := true, z endwait;

у := y*x

od JJ

После выполнения программы справедливо высказывание у = n!. Z

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

Пример (недетерминизм в параллельных программах с критическими об­ластями). Программа

[Г await true then z := 1 endwait || await true then z := 2 endwait jj

допускает выполнение

await true then z := 1 endwait; await true then z := 2 endwait

с конечным состоянием z = 2, а также выполнение

await true then z := 2 endwait; await true then z := I endwait

с конечным состоянием z = 1. С

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

channel m с есть var queue m с := emptyqueue,

send E on с есть await true then с := stock(c, E) endwait,

receive v on с есть await Hsempty(c) then v, с := firsl(c),

rest(c) endwait,

Это показывает, что буферизованные посылки/приемы сообщений также можно понимать как использование особой вычислительной структуры (а именно структуры очереди) в форме общих переменных.

Особые общие переменные являются семафорами - они служат ис­ключительно для синхронизации процессов. Рааличают булевские и це­лочисленные семафоры. Целочисленный семафор s вводится в употреб­ление с помощью объявления

sema nat х: = п.

Это равносильно объявлению программной переменной типа nat, с точ­ностью до следующих ограничений на использование. Семафор s разре­шается использовать только с помощью вызовов процедур P(s) и V(s), а не какими-либо присваиваниями или опросами. Процедуры Р и V опре­деляются следующим образом:

proc Р = (sema nat х) : await х > 0 then х := х - 1 endwait, proc V = (sema nat x) : await true then x := x + 1 endwait.

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

Пример (задача производитель/потребитель, координируемая с помощью семафоров). Следующая программа не удоалетворяет условию Бернштей- на (пусть b - любая булевская функция):

Г sema nat si, s2 := О, 1;

var ш x, у, z := х0, Уо, z0; while -b(x) do P(sl);

x := у; V(s2); consume (x)

od

II

while -^b(z) do produce_next(z); P(s2); У := z; V(sl)

od JJ J

Переменная e используется в одной программе для чтения, а в другой - для записи. Однако благодаря использованию семафора одновременный доступ к общей переменной у исключается. С

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

Булевские семафоры вводятся с помощью объявления

sema bool s := b

и изменяются с помощью следующих процедур:

ргос Р = ( sema bool s ) . await s then s := false endwait, proc V = ( sema bool s ) : await true then s := true endwait.При использовании семафоров в незавершающихся программах снова ставится вопрос о справедливости выполнения операций над семафора­ми. При V-операциях можно без ограничений требовать, чтобы каждый вызов завершался успешно, однако это не имеет места дтя Р-оиерапий. Вызовы Р-операций могут приводить к ситуациям ожидания, если значе­ние семафора есть 0, или false. Если одновременно ожидают многие Р-вызовы семафора, то после выполнения V-вызова продолжается один Р-вызов. Выбор подлежащего продолжению вызова происходит недетер­минированно. При этом без предположения справедливости может слу­читься, что определенный Р-вызов никогда не будет продолжаться, хотя постоянно выполняются V-вызовы, поскольку всегда будут выбираться дтя продолжения другие ожидающие Р-вызовы. В этом случае говорят о старвации ("морении голодом", бесконечном ожидании; англ. starvation) вызова и соответствующей программы.

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

Вместо охраняемых критических областей находят также применение мониторы (Hoare, 1972; Brinch Hansen, 1972), чтобы координировать дос­туп к общим переменным. С помощью монитора осуществляется управ­ление доступом к семейству общих переменных. Монитор включает в се­бя некоторое число функций, процедур и программных переменных. Процедуры доступны (видимы) извне и могут вызываться из парагтельно выполняющихся программ. Основное правило состоит в следующем: в мониторе в каждый момент времени может быть активным не более одного вызова процедуры (взаимные исключения). При этом вместо се­мафоров используются так называемые сигналы, которые очень похожи па булевские семафоры. С точки зрения обмена сообщениями сигналы дают нам возможность ожидать определенных простых сообщений.

Пример (задача производитель/потребитель с помощью монитора). При­водимый ниже монитор регулирует доступ к программной переменной q с помощью доступных извне процедур send и receive;

monitor EV =

Г var queue ш q; signal nonempty;

proc send = (m x) : Г q := stock(q, x); nonempty.signal J; proc receive = (var m y):

Г if isempty(q) then nonempty/wait fi;

q, у := rest(q), first(q) J;

q := emptyqueue; J;

var m x, z := xq, zo;

while -'b(x) do EV.receive(x);

consume (x)

od

|| while ~,b(z) do produce_next(z);

EV.send(z)

od ' Л

Сигнал nonempty при этом соответствует булевской переменной, причем

nonempty.signal соответствует nonempty:=true; nonempty.wait соответствует nonempty:=fa!se;

await nonempty then nop endwait.

Обратим внимание, что взаимные исключения доступа к переменным монитора обеспечиваются концепцией монитора. А именно: при мони­торах предполагается, что каждая из процедур при взаимных исключени­ях выполняется до тех пор, пока процедура успешно завершится или же по сигналу наступит ситуация ожидания. О

Наряду с описанными здесь существует много других более или менее равноценных предложений о языковых элементах для коммуникации и синхронизации операторов в параллельно выполняющихся программах. Однако они представляют собой лишь вариации обсужденных концеп­ций.