Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
03-28-12 Параллельные вычисления.Семафоры, мони...doc
Скачиваний:
14
Добавлен:
23.08.2019
Размер:
384 Кб
Скачать

8.2. Мониторы

Межпроцессное взаимодействие с использованием семафоров выглядит довольно просто. Но это кажущаяся простота. Семафоры обеспечивают достаточно мощный и гибкий инструмент для осуществления взаимных исключений и координации процессов. Однако создать корректно работающую программу с использованием семафоров не всегда легко. Сложность заключается в том, что операции wait и signal могут быть разбросаны по всей программе, и не всегда можно отследить их взаимодействие на контролируемые ими семафоры. Использовать семафоры нужно очень аккуратно и осторожно, так как одна незначительная ошибка (например, как мы уже знаем - простая перестановка двух операций wait) может привести к останову системы. Это напоминает программирование на ассемблере, но на самом деле еще сложнее.

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

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

Компилятор обрабатывает вызовы процедур монитора особым образом. Обычно, когда процесс вызывает процедуру монитора, то первые несколько инструкций этой процедуры проверяют, не активен ли какой-либо другой процесс по отношению к этому монитору. Если да, то вызывающий процесс приостанавливается, пока другой процесс не освободит монитор. Таким образом, исключение входа нескольких процессов в монитор реализуется не программистом, а компилятором, что делает ошибки менее вероятными.

Впервые формальное определение концепции монитора с некоторыми отличиями было дано в 1974 году Хоаром (Hoare) и Бринч Хансеном (Brinch Hansen). Рассмотрим монитор версии Хоара.

8.2.1. Мониторы с сигналами (мониторы Хоара)

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

  1. Локальные переменные монитора доступны только его процедурам.

  2. Процесс входит в монитор путем вызова одной из его процедур.

  3. В мониторе в определенный момент времени может выполняться только один процесс; любой другой процесс, вызывающий монитор, будет приостановлен в ожидании доступности монитора.

Первые две характеристики заставляют нас вспомнить об объектах в ООП. Фактически объектно-ориентированные языки программирования могут легко организовать монитор как объект со специальными характеристиками.

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

На абстрактном уровне можно описать структуру монитора следующим образом:

monitor monitor_name {

описание внутренних переменных ;

void m1(...){...

}

void m2(...){...

}

...

void mn(...){...

}

{

блок инициализации

внутренних переменных;

}

}

Здесь функции m1,..., mn представляют собой функции-методы монитора, а блок инициализации внутренних переменных содержит операции, которые выполняются один и только один раз: при создании монитора или при самом первом вызове какой-либо функции-метода до ее исполнения.

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

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

  1. cwait (c) – приостанавливает выполнение процесса по условию с.

  2. csignal(c) – возобновляет выполнение некоторого процесса, приостановленного вызовом cwait (c) с тем же условием. Если имеется несколько таких процессов, выбирается один из них. Если таких процессов нет, функция не делает ничего.

Обратите внимание на то, что операции wait/ signal монитора отличаются от соответствующих операций семафора. Если процесс в мониторе передает сигнал, но при этом нет ни одного ожидающего его процесса, то сигнал теряется.

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

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

В качестве примера использования монитора вернемся к задаче “производитель-потребитель” с ограниченным буфером. В листинге 8.4 показано решение этой задачи с использованием монитора. Модуль монитора ProducerConsumer управляет буфером для хранения и получения символов. Монитор включает две переменные условий: notfull истинно, если в буфере имеется место хотя бы для одного символа, а notempty истинно, если в буфере имеется хотя бы один символ.

Листинг 8.4. Решение задачи “производитель-потребитель” с ограниченным буфером с использованием монитора Хоара

Monitor ProducerConsumer;

Char buffer [N] /* Место для N элементов */

Int nextin, nextout; /* Указатели буфера */

Int count; /* Количество элементов в буфере */

Int notfull, notempty; /* Синхронизация */

Void Append (char x)

{

if (count == N)

cwait (notfull); /* Буфер заполнен */

Buffer [nextin] = x;

Nextin = (nextin+1) % N;

Count++; /* Добавляем элемент в буфер */

Csignal (notempty); /* Возобновление работы потребителя */

}

Void Take (char x)

{

if (count == 0)

cwait (notempty); /* Буфер пуст */

x = Buffer [nextout];

Nextout = (nextout+1) % N;

Count--; /* Удаляем элемент из буфер */

Csignal (notfull); /* Возобновляем работу производителей */

}

{ /* Тело монитора */

nextin = 0; /* Изначально буфер пуст */

nextout = 0;

cont = 0;

}

Void Producer ()

{

char x;

While (true)

{

Produce (x);

Append (x);

}

}

Void Consumer ()

{

char x;

While (true)

{

Take (x);

Consume (x);

}

}

Void Main ()

{

Perbegin (Producer, Consumer);

}

Производитель может добавлять символы в буфер только из монитора при помощи процедуры Append; прямого доступа к буферу у него нет. Сначала процедура проверяет условие notfull, чтобы выяснить, имеется ли в буфере пустое место. Если его нет, процесс приостанавливается, и в монитор может войти другой процесс (производитель или потребитель). Позже, когда в буфере появится свободное место, приостановленный процесс извлекается из очереди и возобновляет свою работу. После того как процесс поместит символ в буфер, он сигнализирует о выполнении условия notempty, что разблокирует процесс потребителя, если он был приостановлен.

Этот пример иллюстрирует разделение ответственности при работе с монитором и при использовании семафора. Монитор автоматически обеспечивает взаимоисключение: одновременное обращение производителя и потребителя к буферу невозможно. Однако программист должен корректно разместить внутри монитора примитивы Cwait и Csignal, для того чтобы предотвратить размещение элемента в заполненном буфере, или выборку из пустого буфера. В случае использования семафоров ответственность как за синхронизацию, так и за взаимоисключения полностью лежит на программисте.

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

Если выполнения условия х не ожидает ни один процесс, вызов Csignal(х) не выполняет никаких действий.

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