- •4.10 Аппаратная реализация взаимоисключения:
- •Testandset (a, b)
- •4.11 Семафоры
- •4.12 Синхронизация процессов при помощи семафоров
- •4.13 Пара "производитель-потребитель"
- •4.14 Считающие семафоры
- •5.2 Мониторы
- •5.3 Простое распределение ресурсов при помощи мониторов
- •5.4 Пример монитора: кольцевой буфер
- •5.5 Пример монитора: читатели и писатели
5.5 Пример монитора: читатели и писатели
В вычислительных системах обычно имеются некоторые процессы, которые читают данные (и называются "читателями"), и другие процессы, которые записывают данные (и называются "писателями"). Так, в системе предварительной продажи авиационных билетов может быть гораздо больше читателей, чем писателей, поскольку к базе данных, содержащей всю существующую информацию о рейсах самолетов, будет произведено много обращений по запросам кассира-оператора, прежде чем пассажир реально выберет и купит билет на конкретный рейс.
Процессы-читатели не меняют содержимого базы данных, поэтому несколько таких процессов могут обращаться к ней одновременно. А процесс-писатель может изменять данные, поэтому он должен иметь монопольный, исключительный доступ к базе данных. Когда работает процесс-писатель, никакие другие писатели и читатели работать не должны. Конечно, такой режим монопольного доступа следует предусматривать только на уровне работы с индивидуальными записями – не обязательно предоставлять процессу-писателю монопольный доступ к целой базе данных.
Проблему проектирования параллельной программы для управления доступом процессов-читателей и писателей к базе данных впервые сформулировали и решили Куртуа, Хейманс и Парнас (Со71).
На рис. 5.3 представлен монитор, решающий задачу читателей и писателей и разработанный на основе алгоритма, который предложили Хоор и Горман (Но74).
monitor читателииписатели;
var
читатели: целое;
ктотопишет: логический;
читатьразрешается, писатьразрешается: условие;
procedure началочтения;
begin
if ктотопишет or очередь(писатьразрешается)
then wait(читатьразрешается);
читатели := читатели + 1;
signal(читaтьpaзpeшaeтcя)
end;
procedure конецчтения;
begin
читатели := читатели – 1;
if читатели = 0 then signal(пиcaтьpaзpeшaeтcя)
end;
procedure началозаписи;
begin
if читатели > 0 or ктотопишет
then wait(писатьразрешается);
ктотопишет := истина
end;
procedure конецзаписи
begin
ктотопишет := ложь;
if очередь(читатьразрешается)
then signal(читaтьpaзpeшaeтcя)
else signal(пиcaтьpaзpeшaeтcя)
end
begin
читатели := 0;
ктотопишет := ложь
end;
Рис. 5.3 Монитор, решающий задачу читателей и писателей.
В каждый конкретный момент времени работать может только один писатель; когда какой-либо писатель работает, логическая переменная "ктотопишет" имеет истинное значение. Ни один из читателей не может работать, во время работы какого-либо писателя. Переменная "читатели" указывает количество активных читателей. Когда число читателей оказывается равным нулю, ожидающий процесс-писатель получает возможность начать работу. Новый процесс-читатель не может продолжить свое выполнение, пока не появится истинное значение условия "читатьразрешается", а новый процесс-писатель – пока не появится истинное значение условия "писатьразрешается".
Когда процессу-читателю нужно произвести чтение, он вызывает процедуру монитора под названием "началочтения", а после завершения чтения – процедуру "конецчтения".
После входа в процедуру "началочтения" новый процесс-читатель сможет продолжить свою работу, если нет процесса-писателя, производящего в данный момент запись или ожидающего очереди на запись. Второе из этих условий необходимо для того, чтобы предотвратить возможность бесконечного откладывания ожидающих процессов-писателей.
Отметим, что процедура "началочтения" завершается выдачей сигнала "читатьразрешается", чтобы следующий ожидающий очереди читатель мог начать чтение. В этом случае следующий процесс-читатель активизируется и выдает находящемуся после него в очереди читателю сигнал о возможности продолжить выполнение. Фактически подобная "цепная реакция" будет идти до тех пор, пока не активизируются все ожидающие процессы-читатели. Во время выполнения такой цепочки действий все вновь приходящие процессы включаются в очередь ожидания. Цепная активизация процессов-читателей – весьма рациональное решение. Поскольку процессы-читатели не мешают друг другу и поскольку они могут выполняться в мультипроцессорных системах параллельно, это эффективный способ обслуживания подобных процессов. Отметим, что во время выполнения цепочки активизации процессов даже вновь приходящие процессы-читатели не могут войти в монитор, поскольку мы соблюдаем правило, согласно которому процессы, уже получившие сигнал активизации, обслуживаются раньше новых процессов.
Когда процесс завершает операцию чтения, он вызывает процедуру "конецчтения", которая уменьшает число читателей на единицу. В конце концов в результате многократного выполнения этой процедуры количество процессов-читателей становится равным нулю;
в этот момент вырабатывается сигнал "писатьразрешается", так что ожидающий процесс-писатель получает возможность продолжить свою работу.
Когда процессу-писателю нужно произвести запись, он вызывает процедуру монитора под названием "началозаписи". Процесс-писатель должен иметь совершенно монопольный доступ к базе данных, поэтому, если в настоящий момент уже есть работающие процессы-читатели или какой-либо активизированный процесс-писатель, данному писателю придется подождать, пока не будет установлено истинное значение условия "писатьразрешается". Когда писатель 'получает возможность продолжить работу, устанавливается истинное значение логической переменной "ктотопишет". Тем самым -для
всех остальных процессов-писателей и читателей доступ к базе данных или ее частям блокируется.
Когда процесс-писатель заканчивает свою работу, он устанавливает для переменной "ктотопишет" ложное значение, тем самым открывая вход в монитор для других процессов. Затем он должен выдать очередному ожидающему процессу сигнал о том, что можно продолжить работу. Должен ли он при этом отдавать предпочтение ожидающему процессу-читателю или ожидающему процессу-писателю? Если он отдаст предпочтение ожидающему писателю, то может возникнуть такая ситуация, когда постоянный поток приходящих писателей приведет к бесконечному откладыванию ожидающих процессов-читателей. Поэтому писатель, завершивший свою работу, прежде всего проверяет, нет ли ожидающего читателя. Если есть, то выдается сигнал "читатьразрешается", так что этот ожидающий читатель получает возможность продолжить работу. Если ни одного ожидающего читателя нет, то выдается сигнал "писатьразре-шается" и получает возможность продолжить выполнение ожидающий писатель.