Мониторы
8.8. Мониторы
Монитор представляет собой набор информационных структур и процедур, которые используются в режиме разделения. Некоторые из этих процедур являются внешними и доступны процессам пользователей, их имена представляют входные точки монитора. Пользователь не имеет доступа к информационным структурам монитора и может воздействовать на них, только обращаясь ко входным точкам. Монитор, таким образом, воплощает принцип инкапсуляции данных. В терминах объектно-ориентированного программирования ресурс, обслуживаемый монитором, представляет собой объект, а входные точки - методы работы с этим объектом. Особенностью монитора, однако, является то, что в его состав входят, так называемые, "процедуры с охраной", которые не могут выполняться двумя процессами одновременно.
Не являясь средством более мощным или гибким, чем рассмотренные выше примитивы, мониторы, однако, представляют значительно более удобный инструмент для программиста, избавляя его от необходимости формировать критические секции, обеспечивая более высокий уровень интеграции данных и предупреждая возможные ошибки во взаимном исключении.
Если в задаче "производители-потребители" процессы программируются пользователем, то вид этих процессов может быть таким: 1 #include <monitor.h> 2 /*== процесс-производитель (может быть отдельным модулем) ==*/ 3 void producer ( void ) { 4 portion work; 5 while (1) { 6 < производство порции в work > 7 putPortion ( &work ); 8 } 9 } 10 /*== процесс-потребитель (может быть отдельным модулем) ==*/ 11 void consumer ( void ) { 12 portion work; 13 while (1) { 14 getPortion ( &work ); 15 < обработка порции в work> 16 } 17 }
Обратим внимание на то, что процессы, во-первых, никоим образом не заботятся о разделении данных, во-вторых, не используют никакие общие данные. Такая "беззаботная" работа процессов, однако, должна быть поддержана монитором, входные точки которого описаны в файле monitor.h, а определение его имеет такой вид: 1 /*== монитор производителей-потребителей (отдельный модуль) ==*/ 2 #define BSIZE ... 3 /* буфер */ 4 static portion buffer [BSIZE]; 5 /* индексы буфера для чтения и записи*/ 6 static int rIndex = 0, wIndex = 0; 7 /* счетчик заполнения */ 8 static int cnt = 0; 9 /* события НЕ_ПУСТ, НЕ_ПОЛОН */ 10 static event nonEmpty, nonFull; 11 /*== процедура занесения порции в буфер ==*/ 12 void guard putPortion ( portion *x ) { 13 /* если буфер полон - ожидать события НЕ_ПОЛОН */ 14 if ( cnt == BSIZE ) wait (nonFull); 15 /* запись порции в буфер */ 16 memcpy ( buffer + wIndex, x, sizeof(portion) ) ; 17 /* модификация индекса записи */ 18 if ( ++wIndex == BSIZE ) wIndex = 0; 19 cnt++; /* подсчет порций в буфере */ 20 /* сигнализация о том, 21 что буфер НЕ_ПУСТ */ 22 signal (nonEmpty); 23 } 24 /*== процедура позучения порции из буфера ==*/ 25 void guard getPortion ( portion *x ) { 26 if ( cnt == 0 ) wait (nonEmpty); 27 memcpy ( x, buffer + rIndex, sizeof(portion) ) ; 28 if ( ++rIndex == BSIZE ) rIndex = 0; 29 cnt++; 30 signal (nonFull); 31 }
В реализации монитора нам пришлось прибегнуть к некоторым новым обозначениям. Во-первых, функции монитора даны с описателем guard (охрана). Это означает, что они должны выполняться в режиме взаимного исключения. В литературе часто употребляется образное сравнение мониторов с комнатой, в которой может находиться только один человек. Такая комната показана на Рисунке 8.2. Если человек (процесс) желает войти в комнату (охраняемую процедуру монитора), то он становится во входную очередь к двери 1, в которой он ожидает (блокируется) до тех пор, пока комната (монитор) не освободится. Дверь 1 (вход) отпирается только в том случае, если комната пуста, пропускает только одного человека и запирается за ним. Дверь 2 (выход) не заперта, когда она открывается, отпирается и дверь 1.