Saltar al contenido principal

Tema 6: Monitores

1. Introducción y motivación​

En los temas anteriores estudiamos cómo resolver la exclusión mutua y la sincronización mediante semáforos. Aunque los semáforos son primitivas muy potentes a nivel de sistema operativo, presentan importantes inconvenientes cuando se construyen sistemas software complejos:

  1. Variables globales no estructuradas: Los semáforos se declaran como variables globales compartidas, lo que impide un diseño modular y desacoplado acorde a la ingeniería del software moderna.
  2. Ambigüedad funcional: Resulta difícil distinguir a simple vista si un semáforo se está empleando para exclusión mutua (semáforo binario o mutex) o para sincronización condicional (señalización entre hilos).
  3. Código de sincronización disperso: Las operaciones wait() y signal() están esparcidas por los métodos de distintos procesos o hilos. Si un hilo olvida ejecutar un signal(), se desvía por un return prematuro o lanza una excepción no capturada, el recurso queda bloqueado indefinidamente (deadlock).
  4. Ausencia de control en tiempo de compilación: En lenguajes de bajo nivel, el compilador no puede verificar si el acceso a los datos compartidos está correctamente protegido; cualquier hilo puede leer o modificar variables compartidas sin adquirir previamente el semáforo correspondiente.

Para resolver estas deficiencias, Per Brinch Hansen (1973) y C.A.R. Hoare (1974) introdujeron el concepto de Monitor como una construcción lingüística de alto nivel pensada para encapsular datos y sincronización dentro de una misma entidad abstracta.


2. Comparativa entre semáforos y monitores​

CriterioSemáforos (Dijkstra, 1965)Monitores (Hoare / Brinch Hansen, 1974)
Nivel de abstracciónBajo nivel (primitiva del sistema operativo).Alto nivel (construcción del lenguaje de programación).
Localización de datosVariables globales accesibles desde cualquier hilo.Variables privadas encapsuladas dentro del monitor.
Exclusión mutuaExplícita: El programador debe recordar invocar wait() y signal().Implícita: Garantizada automáticamente por el compilador y el entorno de ejecución.
SincronizaciónMediante el mismo contador entero del semáforo.Mediante Variables de Condición independientes (delay/resume).
Propensión a erroresAlta: Un fallo puntual puede congelar todo el sistema.Reducida: El acceso a los datos solo es posible a través de métodos públicos seguros.
Esquema con Semáforos

Semáforos: Sincronización dispersa y datos desprotegidos.

Esquema con Monitores

Monitores: Encapsulación de estado, cerrojo y métodos.


3. Modelo de monitor: concepto y funcionamiento​

Un Monitor es un Tipo Abstracto de Datos (TAD) que encapsula:

  1. El estado privado (datos y variables compartidas).
  2. Un conjunto de operaciones o procedimientos públicos que constituyen la única interfaz de acceso a dicho estado.
  3. Un mecanismo automático de exclusión mutua: en cualquier instante de tiempo, como máximo un hilo puede estar ejecutando código dentro del monitor.
MONITOR
┌────────────────────────────────────────────────────────────────────────┐
│ Cola de Entrada al Monitor (Hilos esperando entrar) │
│ ──► [ Hilo A ] [ Hilo B ] [ Hilo C ] ──► (Cerrojo del Monitor) │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ Procedimiento en ejecución (Máx. 1 hilo activo) │ │
│ │ │ │
│ │ Variables de estado privadas (ocultas): │ │
│ │ int saldo; int ocupados; Cola buffer; │ │
│ │ │ │
│ │ Variables de Condición (Hilos esperando condiciones lógicas): │ │
│ │ cond_t hay_sitio; ──► Cola FIFO: [ Hilo D ] [ Hilo E ] │ │
│ │ cond_t hay_elementos; ──► Cola FIFO: [ Hilo F ] │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Salida del monitor (libera el cerrojo para el siguiente hilo) │
└────────────────────────────────────────────────────────────────────────┘
Estructura formal de un monitor

Estructura formal de un monitor con colas de condición.

3.1. Sintaxis canónica en pseudocódigo​

monitor NombreMonitor:
# 1. Variables privadas internas (estado del recurso)
variables locales;
condition c1, c2; # Variables de condición

# 2. Interfaz pública exportada
export operacion1, operacion2;

procedure operacion1(parámetros):
# Código ejecutado en exclusión mutua
...

procedure operacion2(parámetros):
# Código ejecutado en exclusión mutua
...

# 3. Código de inicialización (se ejecuta al instanciar el monitor)
inicializacion:
# Estado inicial

3.2. Reglas fundamentales de ejecución​

  • Monitor libre: Si un hilo invoca un procedimiento del monitor y ningún otro hilo está dentro, accede de inmediato y toma el cerrojo implícito.
  • Monitor ocupado: Si otro hilo ya está ejecutando un procedimiento dentro del monitor, el hilo invocador se suspende y queda encolado en la cola de entrada al monitor.
  • Salida del monitor: Cuando el hilo activo finaliza la ejecución de su procedimiento, el cerrojo se libera automáticamente y se permite el paso al primer hilo de la cola de entrada.

4. Variables de condición y sus operaciones​

La exclusión mutua por sí sola no basta: con frecuencia un hilo entra al monitor pero comprueba que el estado actual no le permite continuar (por ejemplo, un consumidor entra pero el buffer está vacío). Si se quedara esperando dentro del monitor, bloquearía a todos los demás hilos (incluidos los productores que podrían llenar el buffer), provocando un interbloqueo.

Para solucionar esto, los monitores incorporan Variables de Condición. Una variable de condición no almacena ningún valor ni contador: es únicamente un nombre para una cola de espera de hilos.

Sincronización condicional en monitores

Un hilo que no puede continuar se suspende en la variable de condición y cede el monitor.

Sobre una variable de condición c se definen las siguientes operaciones primitivas:

4.1. Operación delay(c) (o wait(c))​

  • El hilo invocador se suspende y se coloca al final de la cola de espera de la condición c.
  • Libera atómicamente el monitor, permitiendo que otro hilo de la cola de entrada (o de cortesía) pueda acceder.
  • El hilo permanecerá dormido hasta que otro proceso ejecute un resume(c).

4.2. Operación resume(c) (o signal(c))​

  • Si hay hilos esperando en la cola de la condición c, despierta exactamente al primero de ellos.
  • Si la cola de c está vacía, la operación no tiene ningún efecto (es una operación sin memoria, a diferencia de los semáforos donde el contador se incrementaría).

4.3. Operación empty(c)​

  • Función booleana de consulta: devuelve true si la cola de la condición c no tiene ningún hilo esperando, y false en caso contrario.
Diferencia fundamental: las variables de condición no tienen memoria

En los semáforos, la operación signal() incrementa el contador de permisos; si ningún hilo estaba esperando, la señal queda almacenada para el futuro.

En las variables de condición de un monitor, la operación resume(c) carece de memoria. Si se invoca resume(c) cuando la cola de c está vacía (empty(c) == true), la señal se pierde definitivamente en el éter. No se incrementa ningún contador ni se recuerda para futuros hilos.

4.4. Especificación de prioridades en colas de condición​

En la formulación formal de Hoare, la operación de espera admite un parámetro de rango o prioridad: delay(c, p).

Los hilos encolados en la variable de condición se ordenan de menor a mayor valor de pp (donde menor valor numérico representa mayor prioridad). Al ejecutar resume(c), se extrae y despierta preferentemente al hilo con menor pp, lo que permite modelar directamente planificadores de recursos con prioridad (como analizaremos en la implementación en C con POSIX).

Funcionamiento de delay y resume

Ciclo de suspensión con delay(c) y reactivación mediante resume(c).


5. Políticas de reanudación y señalización​

Cuando un hilo señalizador (S) ejecuta resume(c) y despierta a un hilo durmiente (W), surge un dilema fundamental de diseño:

Dado que en un monitor solo puede haber un hilo activo a la vez, ¿cuál de los dos hilos debe continuar ejecutando dentro del monitor inmediatamente después del resume(c)?

La respuesta a esta pregunta define la política de señalización del monitor. Existen tres disciplinas principales:

┌─────────────────────────────────────────────────────────────┐
│ POLÍTICAS DE SEÑALIZACIÓN EN MONITORES │
├──────────────────────────────┬──────────────────────────────┤
│ 1. Hoare (IRR / Urgent Wait) │ 2. Mesa / Java / POSIX (SC) │
├──────────────────────────────┼──────────────────────────────┤
│ - Señalar y Esperar Urgente │ - Señalar y Continuar │
│ - W se ejecuta de INMEDIATO │ - S continúa ejecutando │
│ - S cede monitor y va a cola │ - W pasa a competir con E │
│ de cortesía/urgente │ │
│ - Condición garantizada │ - Condición puede ser robada │
│ - Guarda con if es suficiente│ - Guarda con while obligada │
└──────────────────────────────┴──────────────────────────────┘

5.1. Señalar y esperar urgente (Signal-and-Urgent-Wait / Hoare / IRR)​

Esta política, introducida por C.A.R. Hoare, también se conoce como IRR (Immediate Resumption Requirement):

  • Mecanismo: Cuando el hilo señalizador S ejecuta resume(c), cede el monitor de forma inmediata y pasa a una cola especial de alta prioridad llamada cola de cortesía o cola urgente. El hilo despertado W toma el control del monitor en ese mismo instante.
  • Prioridad en el acceso al monitor: Cola de Entrada (E)<Sen˜alizador en Cola Urgente (S)<Hilo Despertado (W)\text{Cola de Entrada } (E) < \text{Señalizador en Cola Urgente } (S) < \text{Hilo Despertado } (W)
  • Garantía del Invariante: Como el hilo despertado entra al monitor inmediatamente sin que ningún otro proceso pueda intercalarse, la condición por la que esperaba sigue siendo estrictamente cierta.
  • Consecuencia sintáctica: Basta con usar una guarda condicional simple:
    if (not condicion):
    delay(c)
    # Aquí la condición es 100% segura

5.2. Señalar y continuar (Signal-and-Continue / Mesa / Java / POSIX)​

Diseñada por Lampson y Redell en Xerox PARC para el sistema operativo Mesa (1980), es la semántica adoptada por Java (notify), POSIX Threads (pthread_cond_signal) y Python (notify):

  • Mecanismo: Cuando el señalizador S ejecuta resume(c), no cede el monitor: continúa ejecutando su código hasta salir normalmente o bloquearse. El hilo despertado W pasa a la cola de listos para competir por el monitor junto con los hilos de la cola de entrada.
  • Prioridad en el acceso: Cola de Entrada (E)=Hilo Despertado (W)<Sen˜alizador (S)\text{Cola de Entrada } (E) = \text{Hilo Despertado } (W) < \text{Señalizador } (S)
  • El fenómeno del "Robo de Condición" (Condition Theft):
    Entre el momento en que W es despertado y el momento en que finalmente consigue entrar al monitor, otro hilo recién llegado de la cola de entrada EE puede colarse y consumir el recurso liberado.
  • Consecuencia sintáctica OBLIGATORIA: Es imperativo evaluar la guarda en un bucle while:
    while (!condicion) {
    wait();
    }
    // Al despertar, reevalúa la condición antes de continuar

5.3. Señalar y salir (Signal-and-Exit / Brinch Hansen)​

  • El hilo señalizador solo puede invocar resume(c) como la última instrucción de su procedimiento, saliendo del monitor en ese mismo acto.
  • Elimina la necesidad de colas de cortesía y garantiza la preservación de la condición, pero restringe la expresividad del lenguaje.

5.4. Traza comparativa paso a paso: IRR (Hoare) frente a Mesa (Java)​

Analicemos la ejecución de la siguiente secuencia de eventos con cuatro hilos (S1,S2,S3,S4S_1, S_2, S_3, S_4):

  1. S1S_1 entra al monitor y ejecuta delay(c).
  2. S2S_2 entra al monitor y ejecuta delay(c).
  3. S3S_3 entra al monitor, realiza un cálculo y ejecuta resume(c).
  4. Inmediatamente después, S4S_4 intenta entrar al monitor desde fuera.

Traza bajo política IRR (Hoare / Immediate Resumption)​

Prioridad: Entrada (EE) < Cola Urgente (SS) < Despertado (WW)

PasoAcción realizadaDentro del monitorCola urgente (SS)Cola de condición (cc)Cola de entrada (EE)
1S1S_1 entra al monitorS1S_1———
2S1S_1 ejecuta delay(c) (se bloquea y cede)(vacío)—S1S_1—
3S2S_2 entra al monitorS2S_2—S1S_1—
4S2S_2 ejecuta delay(c) (se bloquea y cede)(vacío)—S1,S2S_1, S_2—
5S3S_3 entra al monitorS3S_3—S1,S2S_1, S_2—
6S4S_4 intenta entrarS3S_3—S1,S2S_1, S_2S4S_4
7S3S_3 ejecuta resume(c) →\rightarrow S3S_3 cede a S1S_1S1S_1S3S_3S2S_2S4S_4
8S1S_1 termina y sale →\rightarrow prioridad a la cola urgenteS3S_3—S2S_2S4S_4
9S3S_3 termina y sale →\rightarrow entra la cola de entradaS4S_4—S2S_2—
10S4S_4 termina y sale(vacío)—S2S_2—
Orden de ejecución efectivo en IRR

El orden en que los hilos ejecutan sus operaciones finales dentro del monitor es: S1→S3→S4S_1 \rightarrow S_3 \rightarrow S_4 (S2S_2 sigue esperando en la condición cc).

Traza bajo política Mesa / Java (Signal-and-Continue)​

Prioridad: Despertado (WW) = Entrada (EE) < Señalizador (SS)

PasoAcción realizadaDentro del monitorCola de condición (cc)Hilos listos compitiendo
1-4S1S_1 y S2S_2 ejecutan delay(c)(vacío)S1,S2S_1, S_2—
5S3S_3 entra al monitorS3S_3S1,S2S_1, S_2—
6S4S_4 intenta entrarS3S_3S1,S2S_1, S_2S4S_4
7S3S_3 ejecuta resume(c) →\rightarrow S3S_3 continúa dentroS3S_3S2S_2S1,S4S_1, S_4
8S3S_3 termina y sale(vacío)S2S_2S1,S4S_1, S_4 (compiten por entrar)
9aSi el planificador elige a S4S_4 (robo de condición):S4S_4S2S_2S1S_1
9bSi el planificador elige a S1S_1:S1S_1S2S_2S4S_4
Consecuencia en Mesa/Java

En el paso 9, S4S_4 puede entrar antes que S1S_1. Si S4S_4 consume el recurso que S3S_3 acababa de producir, cuando S1S_1 finalmente entre encontrará que la condición ya no se cumple. Por esta razón, en Mesa/Java es obligatorio comprobar con while.


6. Problema de la cuenta de ahorros​

Una cuenta bancaria compartida admite dos operaciones:

  • depositar(cantidad): ingresa saldo y notifica a los clientes que esperan para retirar.
  • sacar(cantidad): retira fondos únicamente si hay saldo suficiente; en caso contrario, se bloquea. El balance nunca puede ser negativo.

6.1. Solución básica (con posibilidad de adelantamiento)​

monitor CuentaAhorros:
integer balance = 0
condition saldo_suficiente
export depositar, sacar

procedure depositar(cantidad):
balance = balance + cantidad
resume(saldo_suficiente) # Despierta a clientes en espera

procedure sacar(cantidad):
while balance < cantidad:
delay(saldo_suficiente)
balance = balance - cantidad
# Si sobra saldo, despierta a otros por si su importe es menor
if balance > 0:
resume(saldo_suficiente)

6.2. Variante sin "colarse": orden FIFO estricto de retirada​

Si un cliente solicita 1.000 € y se bloquea, no es justo que un cliente posterior que pide 10 € se cuele y retire dinero antes que él. Para garantizar equidad (fairness):

monitor CuentaAhorrosEstricta:
integer balance = 0
integer turno_actual = 0
integer siguiente_turno = 0
condition cola_turnos
export depositar, sacar

procedure depositar(cantidad):
balance = balance + cantidad
resume(cola_turnos)

procedure sacar(cantidad):
integer mi_turno = siguiente_turno
siguiente_turno = siguiente_turno + 1

# Solo puede retirar si es su turno Y hay balance suficiente
while mi_turno != turno_actual or balance < cantidad:
delay(cola_turnos)

balance = balance - cantidad
turno_actual = turno_actual + 1
resume(cola_turnos) # Pasa el testigo al siguiente en turno
Demostración ejecutable en Python

El script cuenta_ahorros.py implementa ambas variantes (CuentaAhorrosInjusta con posibles adelantamientos e inanición frente a CuentaAhorrosJustaFIFO con turnos estrictos) y muestra por consola cómo los turnos ordenan los reintegros equitativamente.


7. Patrones y problemas clásicos con monitores​

7.1. Productor-consumidor con búfer acotado​

El monitor gestiona un búfer circular de tamaño NN empleando dos variables de condición:

  • no_lleno: donde duermen los productores cuando el búfer tiene NN elementos.
  • no_vacio: donde duermen los consumidores cuando el búfer tiene 0 elementos.
import threading
from collections import deque

class BoundedBuffer:
def __init__(self, capacidad):
self.capacidad = capacidad
self.buffer = deque()
self.mutex = threading.Lock()
self.no_lleno = threading.Condition(self.mutex)
self.no_vacio = threading.Condition(self.mutex)

def producir(self, elemento):
with self.mutex:
while len(self.buffer) == self.capacidad:
self.no_lleno.wait()

self.buffer.append(elemento)
self.no_vacio.notify() # Despierta a un consumidor

def consumir(self):
with self.mutex:
while len(self.buffer) == 0:
self.no_vacio.wait()

elemento = self.buffer.popleft()
self.no_lleno.notify() # Despierta a un productor
return elemento

7.2. Lectores-escritores (prioridad a escritores sin inanición)​

Para evitar que una marea continua de lectores provoque la inanición de los escritores, introducimos un contador de escritores en espera (escritores_esperando):

import threading

class ReaderWriterPriority:
def __init__(self):
self.lectores = 0
self.escribiendo = False
self.escritores_esperando = 0
self.mutex = threading.Lock()
self.puede_leer = threading.Condition(self.mutex)
self.puede_escribir = threading.Condition(self.mutex)

def iniciar_lectura(self):
with self.mutex:
# Si hay un escritor escribiendo O escritores en cola, el lector espera
while self.escribiendo or self.escritores_esperando > 0:
self.puede_leer.wait()
self.lectores += 1

def terminar_lectura(self):
with self.mutex:
self.lectores -= 1
if self.lectores == 0:
self.puede_escribir.notify() # El último lector despierta al escritor

def iniciar_escritura(self):
with self.mutex:
self.escritores_esperando += 1
while self.escribiendo or self.lectores > 0:
self.puede_escribir.wait()
self.escritores_esperando -= 1
self.escribiendo = True

def terminar_escritura(self):
with self.mutex:
self.escribiendo = False
# Preferencia a escritores si los hay; si no, despierta a los lectores
if self.escritores_esperando > 0:
self.puede_escribir.notify()
else:
self.puede_leer.notify_all()

7.3. Los filósofos comensales (adquisición atómica de palillos)​

Con monitores podemos resolver el problema de los filósofos eliminando el interbloqueo de raíz mediante la adquisición atómica: un filósofo solo toma sus palillos si ambos están libres simultáneamente.

import threading

class DiningPhilosophers:
PENSANDO = 0
HAMBRIENTO = 1
COMIENDO = 2

def __init__(self, n=5):
self.n = n
self.estado = [self.PENSANDO] * n
self.mutex = threading.Lock()
self.puede_comer = [threading.Condition(self.mutex) for _ in range(n)]

def _comprobar(self, i):
izq = (i - 1) % self.n
der = (i + 1) % self.n
# Solo come si tiene hambre y ninguno de sus dos vecinos está comiendo
if (self.estado[i] == self.HAMBRIENTO and
self.estado[izq] != self.COMIENDO and
self.estado[der] != self.COMIENDO):
self.estado[i] = self.COMIENDO
self.puede_comer[i].notify()

def coger_palillos(self, i):
with self.mutex:
self.estado[i] = self.HAMBRIENTO
self._comprobar(i)
while self.estado[i] != self.COMIENDO:
self.puede_comer[i].wait()

def soltar_palillos(self, i):
with self.mutex:
self.estado[i] = self.PENSANDO
# Comprueba si sus vecinos pueden comer ahora que ha liberado los palillos
self._comprobar((i - 1) % self.n)
self._comprobar((i + 1) % self.n)

7.4. Barrera de sincronización para NN hilos​

Una barrera detiene a todos los hilos concurrentes hasta que el último de ellos haya alcanzado el punto de encuentro:

import threading

class Barrier:
def __init__(self, total_hilos):
self.total = total_hilos
self.llegados = 0
self.mutex = threading.Lock()
self.todos_llegaron = threading.Condition(self.mutex)

def esperar(self):
with self.mutex:
self.llegados += 1
if self.llegados == self.total:
self.llegados = 0 # Reutilizable para la siguiente fase
self.todos_llegaron.notify_all()
else:
self.todos_llegaron.wait()

7.5. El problema de la olla de la tribu​

Una tribu salvaje cena comunitariamente de una gran olla que contiene como máximo MM raciones. Cuando un salvaje tiene hambre, se sirve de la olla; si está vacía, despierta al cocinero y espera a que este la llene por completo. Solo se debe despertar al cocinero cuando la olla llega a 0 raciones.

monitor OllaTribu:
integer raciones = M
integer MAX_RACIONES = M
condition olla_vacia # Cola donde duerme el cocinero
condition olla_con_comida # Cola donde duermen los salvajes hambrientos
export servirse_porcion, rellenar_olla

procedure servirse_porcion():
while raciones == 0:
resume(olla_vacia) # Despierta al cocinero
delay(olla_con_comida) # El salvaje espera a que se cocine

raciones = raciones - 1
# El salvaje se sirve y come su porción

procedure rellenar_olla():
while raciones > 0:
delay(olla_vacia) # El cocinero duerme si aún queda comida

raciones = MAX_RACIONES # Cocina un nuevo festín de M raciones
resumeAll(olla_con_comida) # Despierta a los salvajes hambrientos
Demostración ejecutable en Python

El script olla_tribu.py implementa el monitor de la tribu con hilos salvajes concurrentes y un hilo cocinero, simulando las comidas comunales y los avisos cuando la olla se vacía.


8. Implementación de monitores en entornos reales​

8.1. En POSIX Threads (pthread en C)​

En sistemas basados en Unix y estándares POSIX (pthread.h), un monitor se construye combinando un cerrojo mutex (pthread_mutex_t) con una o varias variables de condición (pthread_cond_t):

#include <pthread.h>

typedef struct {
int contador;
pthread_mutex_t cerrojo;
pthread_cond_t condicion;
} MonitorEjemplo;

void monitor_init(MonitorEjemplo *m) {
m->contador = 0;
pthread_mutex_init(&m->cerrojo, NULL);
pthread_cond_init(&m->condicion, NULL);
}

void monitor_esperar_par(MonitorEjemplo *m) {
pthread_mutex_lock(&m->cerrojo);

// Obligatorio bucle while (política Mesa / Signal-and-Continue)
while ((m->contador % 2) != 0) {
// Libera el mutex atómicamente y duerme en la condición
pthread_cond_wait(&m->condicion, &m->cerrojo);
}

// Sección crítica con invariante garantizado
m->contador++;

pthread_mutex_unlock(&m->cerrojo);
}

void monitor_incrementar(MonitorEjemplo *m) {
pthread_mutex_lock(&m->cerrojo);

m->contador++;
// Despierta a un hilo esperando en la condición
pthread_cond_signal(&m->condicion);

pthread_mutex_unlock(&m->cerrojo);
}

8.2. Emulación formal de monitores con semáforos (paso de testigo de Hoare)​

Para demostrar que los monitores son computacionalmente equivalentes a los semáforos, C.A.R. Hoare demostró cómo implementar un monitor con disciplina IRR (Señalar y Esperar Urgente) utilizando semáforos binarios mediante la técnica del paso de testigo:

Estructura de datos requerida​

  1. Semáforo m_mutex (inicializado a 1): Controla la entrada inicial al monitor.
  2. Semáforo m_next (inicializado a 0): Cola urgente o de cortesía para los hilos que acaban de ejecutar resume() y deben ceder el monitor temporalmente.
  3. Entero m_next_count (inicializado a 0): Número de hilos esperando en la cola urgente.
  4. Por cada variable de condición xx:
    • Semáforo m_x_sem (inicializado a 0).
    • Entero m_x_count (inicializado a 0): número de hilos esperando en la condición xx.

Esqueleto de cada procedimiento público del monitor​

wait(m_mutex); // 1. Adquiere el monitor en exclusión mutua

// --- CUERPO DEL PROCEDIMIENTO ---

// 2. Protocolo de salida (Da preferencia estricta a la cola urgente):
if (m_next_count > 0) {
signal(m_next); // Cede el testigo al hilo de la cola urgente
} else {
signal(m_mutex); // Si no hay nadie en cola urgente, libera la entrada
}

Operaciones sobre la variable de condición xx​

void delay(x) {
m_x_count++;

// Al bloquearse, debe ceder el monitor a alguien:
if (m_next_count > 0) {
signal(m_next); // Prioridad a la cola urgente
} else {
signal(m_mutex); // Si no hay nadie urgente, libera la entrada general
}

wait(m_x_sem); // El hilo se suspende en la condición x
m_x_count--;
}

void resume(x) {
if (m_x_count > 0) {
m_next_count++;

signal(m_x_sem); // Despierta al hilo esperando en la condición x
wait(m_next); // El señalizador se suspende de inmediato en la cola urgente

m_next_count--;
}
// Si m_x_count == 0, resume no hace nada (sin memoria)
}
¿Por qué esta emulación preserva la exclusión mutua sin perder el cerrojo?

Obsérvese que cuando un hilo hace signal(m_x_sem), no ejecuta signal(m_mutex). Pasa directamente el permiso de ejecución (el "testigo") al hilo que despierta en m_x_sem, y él mismo se suspende en wait(m_next).

El número de hilos activos dentro del código del monitor es siempre estrictamente 1, garantizando la política IRR de Hoare de forma matemáticamente exacta.


9. Código fuente disponible​

Todos los ejemplos analizados a lo largo de este tema se encuentran disponibles para su consulta, descarga y ejecución:

  • producer-consumer.py: Productor-consumidor con búfer acotado y variables Condition.
  • rw_lock.py: Cerrojo Lectores-Escritores sin inanición.
  • philosophers.py: Filósofos comensales con adquisición atómica de palillos.
  • barrier.py: Barrera de sincronización reusable para NN hilos.
  • cuenta_ahorros.py: Cuenta de ahorros comparando inanición frente a turnos FIFO estrictos.
  • olla_tribu.py: Problema de los salvajes y el cocinero con la olla comunal.
  • MonitorPosix/: Implementación completa en C de una cola con prioridad (delay(c, p)) mediante pthread_mutex_t y pthread_cond_t.