2. Introducción
Los semáforos, a pesar de su sencillez de uso, son el equivalente a
las instrucciones goto y el manejo de apuntadores en los lenguajes
de programación imperativos: son muy susceptibles a errores. Su
utilización exige disciplina. Por ejemplo, el siguiente error conduce
inmediatamente a un deadlock:
P(S) ; sección crítica P(S);
Generalmente resulta difícil distinguir entre los dos usos de los
semáforos (i.e. para exclusión mutua y condición de
sincronización) en un programa sin una revisión detallada de todo
el código.
Los monitores pretenden ayudar a evitar los riesgos a que se
presentan esos tipos de errores de programación.
3. Definición
Un monitor es un módulo opaco que encapsula servicios mediante
métodos de acceso, así como sus variables locales y globales. La única
forma para manipular o acceder las variables dentro del monitor es
invocando alguno de los métodos de servicio. Solamente se permite que
un hilo esté activo a la vez dentro del monitor ejecutando uno de los
métodos de servicio, asegurando exclusión mutua y previniendo
implícitamente la presencia de condiciones de contención. Cada objeto
monitor tiene un candado, el compilador del lenguaje de programación
genera el código al comienzo de cada método de servicio para adquirir el
candado y al final para liberarlo. Si el monitor está ocupado por algún hilo
(i.e. se apropió del candado), los hilos siguientes que invoquen alguno de
los métodos de servicio del monitor (i.e. intenten entrar al monitor) serán
bloqueados e incorporados en la lista de espera para adquirir el candado.
4. Al igual que los semáforos, los monitores ofrecen las dos formas de
sincronización: la exclusión mutua está garantizada por el
compilador implícitamente al invocar métodos de servicio. Para
proporcionar mecanismos para sincronización de eventos
(condición de sincronización) un monitor puede contener variables
de condición, las cuales pueden manipularse mediante las
operaciones signal y wait (que son análogas a las operaciones P y V
en semáforos binarios, respectivamente). Su funcionamiento se
describe enseguida:
wait: Un hilo que espera a que ocurra un evento indicado por una
variable de condición deja al monitor temporalmente, libera el
candado y se une a la lista de hilos bloqueados correspondiente a
esa variable de condición.
5. signal: Cada señal con respecto a una variable de condición
despierta a un hilo de la lista de hilos bloqueados
correspondiente a esa variable de condición (no
necesariamente el que lleva más tiempo en espera), si no
hay ningún hilo esperando, la señal no se almacena y no
tiene efecto (contrastando a la manera en la cual los
semáforos funcionan). Dado que las operaciones de liberar
al candado y unirse a la lista de espera por una variable de
condición son operaciones atómicas no hay riesgo de
pérdida de las señales (i.e. `wakeup'). Al hilo despertado
por la señal se le desplaza de esa lista de bloqueados y se le
coloca en la lista de hilos en espera por entrar al monitor.
Una vez que el candado sea readquirido, el hilo en cuestión
continúa la ejecución del método de servicio que invocó
anteriormente (i.e. la primera vez para entrar al monitor).
6. Ejemplo
Como ejemplo, mostramos (en pseudo código) un fragmento para la
solución al problema del productor-consumidor utilizando un buffer
limitado.
public synchronized void deposit(double data) {
if (count == size) wait(notFull);
buf[rear] = data;
4 rear = (rear+1) % size;
count++;
if (count == 1) notify(notEmpty);
}
public synchronized double fetch() {
double result;
if (count == 0) wait(notEmpty);
result = buf[front];
front = (front+1) % size;
count--;
if (count == size-1) notify(notFull);
return result;
}
7. Si el buffer está lleno, el productor se bloquea con la invocación al
método wait respecto a la variable de condición notFull, el productor es
despertado por el consumidor mediante la señal notify cuando deja un
espacio libre en el buffer.
Si el buffer está vacío, el consumidor se bloquea respecto la variable
notEmpty y a su vez será despertado por el productor cuando éste
coloque un elemento en el buffer.
Para la disciplina signal and continue, no se requiere que el hilo que
emitió la señal salga del monitor, ni tampoco que el hilo despertado tenga
prioridad para proceder dentro del monitor sobre los demás hilos que
están esperando entrar (compiten por el candado).