Мониторы
Решение задачи производитель-потребитель с помощью семафоров выглядит элегантно, но программирование с их использованием требует повышенной осторожности и внимания и поэтому негодится для сложных программ. Поэтому в 1974 году Хоаром был предложен механизм еще более высокого уровня, чем семафоры, получивший название мониторов.
Мониторы представляют собой тип данных, который может быть внедрен в объектно-ориентированные языки программирования. Монитор обладает своими собственными переменными, определяющими его состояние. Значения этих переменных извне монитора могут быть изменены только с помощью вызова функций-методов, принадлежащих монитору. В свою очередь, эти функции-методы могут использовать в своей работе только данные, находящиеся внутри монитора и свои параметры. На абстрактном уровне можно описать структуру монитора следующим образом:
monitor monitor_name
{
описание переменных ;
void m1(...) {..}
void m2(...) {...}
...
void mn(...) {...}
{
блок инициализации внутрениих переменных ;
}
}
Здесь функции m1,..., mn представляют собой функции-методы монитора, а блок инициализации внутренних переменных содержит операции, которые выполняются только один раз: при создании монитора или при самом первом вызове какой-либо функции-метода до ее исполнения.
Важной особенностью мониторов является то, что в любой момент времени только один процесс может быть активен, т. е. находиться в состоянии «готовность» или«исполнение», внутри данного монитора. Поскольку мониторы представляют собой особые конструкции языка программирования, то компилятор может отличить вызов функции, принадлежащей монитору, от вызовов других функций и обработать его специальным образом, добавив к нему пролог и эпилог, реализующий взаимоисключение. Так как обязанность конструирования механизма взаимоисключений возложена на компилятор, а не на программиста, работа программиста при использовании мониторов существенно упрощается, а вероятность появления ошибок становится меньше.
Однако одних только взаимоисключений не достаточно для того, чтобы в полном объеме реализовать решение задач, возникающих при взаимодействии процессов. Необходимы также средства организации очередности процессов, подобно семафорам full иempty в предыдущем примере. Для этого в мониторах было введено понятие условных переменных (condition variables), над которыми можно совершать две операцииwait иsignal, до некоторой степени похожие на операции P и Vнад семафорами.
Если функция монитора не может выполняться дальше, пока не наступит некоторое событие, она выполняет операцию wait над какой-либо условной переменной. При этом процесс, выполнивший операцию wait, блокируется, становится неактивным, и другой процесс получает возможность войти в монитор.
Когда ожидаемое событие происходит, другой процесс внутри функции-метода совершает операцию signalнад той же самой условной переменной. Это приводит к пробуждению ранее заблокированного процесса, и он становится активным. Если несколько процессов дожидались операции signal для этой переменной, то активным становится только один из них. Чтобы не оказалось двух процессов, разбудившего и пробужденного, одновременно активных внутри монитора, Хоар предложил, чтобы пробужденный процесс подавлял исполнение разбудившего процесса, пока он сам не покинет монитор. Несколько позже Хансен предложил другой механизм: разбудивший процесс покидает монитор немедленно после исполнения операции signal. Остановимся на подходе Хансена.
Применим концепцию мониторов к решению задачи производитель-потребитель.
monitor ProducerConsumer
{
condition full, empty;
int count;
void put()
{
if(count == N) full.wait;
put_item;
count += 1;
if(count == 1) empty.signal;
}
void get()
{
if (count == 0) empty.wait;
get_item();
count -= 1;
if(count == N-1) full.signal;
}
{
count = 0;
}
}
Producer:
while(1)
{
produce_item;
ProducerConsumer.put();
}
Consumer:
while(1)
{
ProducerConsumer.get();
consume_item;
}
Приведенный пример решает поставленную задачу.
Реализация мониторов требует разработки специальных языков программирования и компиляторов для них (например, параллельный Паскаль, Java и т.д.). Эмуляция мониторов с помощью системных вызовов для обычных широко используемых языков программирования не так проста, как эмуляция семафоров. Поэтому можно пользоваться еще одним механизмом со скрытыми взаимоисключеньями, – передачей сообщений.