1. Qué es un monitor
Un monitor es una abstracción de sincronización que encapsula datos compartidos y las operaciones (métodos) que los manipulan, garantizando que solo un hilo a la vez puede ejecutar cualquier operación del monitor. La exclusión mutua es implícita: el programador no necesita bloquear ni desbloquear un mutex manualmente.
El monitor es una construcción de alto nivel que combina datos y sincronización en una sola abstracción. Es como un objeto (en programación orientada a objetos) que es thread-safe por construcción.
La idea es simple: si un monitor encapsula un contador, todos los métodos del monitor (incrementar, decrementar, leer) se ejecutan de forma mutuamente excluyente. El programador solo escribe la lógica; el monitor se encarga de la sincronización.
Un monitor es como una clase en POO, pero con la garantía de que solo un hilo ejecuta un método a la vez. La exclusión mutua es automática, no manual.
2. Origen: Hoare y Brinch Hansen
El concepto de monitor fue propuesto independientemente por C.A.R. Hoare y Per Brinch Hansen en 1974. Hoare publicó «Monitors: An Operating System Structuring Concept» y Brinch Hansen publicó «Structured Multiprogramming».
Hoare definió el monitor como una construcción del lenguaje de programación, no como una biblioteca. En un lenguaje con monitores, el compilador genera automáticamente el código de sincronización (lock/unlock) alrededor de los métodos del monitor.
Brinch Hansen implementó monitores en el lenguaje Concurrent Pascal, que fue uno de los primeros lenguajes con soporte para concurrencia.
3. Estructura de un monitor
Un monitor tiene esta estructura:
monitor NombreMonitor {
/* Variables compartidas (privadas). */
int dato;
condition cond;
/* Métodos (operaciones). */
void metodo1() {
/* exclusión mutua implícita */
/* ... */
}
int metodo2() {
/* exclusión mutua implícita */
/* ... */
return dato;
}
}
Las características son:
- Datos privados: las variables compartidas solo son accesibles a través de los métodos del monitor.
- Exclusión mutua implícita: solo un hilo puede ejecutar un método a la vez.
- Variables de condición: para esperar a que se cumpla una condición.
- Métodos: las operaciones que manipulan los datos.
4. Exclusión mutua implícita
La exclusión mutua implícita es la propiedad más importante del monitor: el programador no necesita bloquear ni desbloquear un mutex. El monitor lo hace automáticamente.
Internamente, el monitor tiene un mutex (o lock) que se adquiere al entrar a un método y se libera al salir. El programador solo escribe la lógica del método; el monitor se encarga de la sincronización.
Esto elimina una fuente importante de errores: olvidar el unlock, bloquear dos veces, etc. El programador no puede cometer estos errores porque no maneja el lock manualmente.
El programador no puede olvidar el unlock porque no hay unlock manual. El monitor lo hace automáticamente al salir del método. Esto elimina una clase entera de errores.
5. Variables de condición en monitores
Los monitores usan variables de condición para esperar a que se cumpla una condición. A diferencia de los semáforos, las variables de condición de un monitor tienen operaciones wait y signal (o notify).
En un monitor, wait hace tres cosas:
- Libera el mutex del monitor. Otros hilos pueden entrar. liberar
- Se bloquea. El hilo se duerme en la cola de la condición. bloquear
- Al despertar, re-adquiere el mutex. Antes de continuar, el hilo bloquea el mutex de nuevo. re-adquirir
signal (o notify) despierta a un hilo bloqueado en la condición. broadcast (o notifyAll) despierta a todos.
6. Semántica de Hoare vs. Mesa
Hay dos semánticas para las variables de condición en monitores, propuestas por Hoare y Mesa:
| Semántica | Qué ocurre al hacer signal | Ventaja | Desventaja |
|---|---|---|---|
| Hoare | El hilo señalador se bloquea y el hilo señalado se ejecuta inmediatamente (con el mutex). | La condición se cumple cuando el hilo señalado se ejecuta. Se puede usar if. | Más cambios de contexto. |
| Mesa | El hilo señalador continúa ejecutándose. El hilo señalado se despierta pero debe esperar el mutex. | Menos cambios de contexto. | La condición puede no cumplirse cuando el hilo señalado se ejecuta (se necesita while). |
7. Semántica de Hoare
En la semántica de Hoare, cuando un hilo hace signal, el hilo señalador se bloquea y el hilo señalado se ejecuta inmediatamente con el mutex. Esto garantiza que la condición se cumple cuando el hilo señalado se ejecuta.
Ventajas:
- La condición se cumple: cuando el hilo señalado se ejecuta, la condición es verdadera.
- Se puede usar if: no se necesitan despertares espurios ni re-verificación.
Desventajas:
- Más cambios de contexto: el hilo señalador se bloquea y el hilo señalado se ejecuta, lo que implica un cambio de contexto.
- Complejidad: la implementación es más compleja.
8. Semántica de Mesa
En la semántica de Mesa, cuando un hilo hace signal, el hilo señalador continúa ejecutándose. El hilo señalado se despierta pero debe esperar a que el mutex se libere antes de continuar.
Ventajas:
- Menos cambios de contexto: el hilo señalador no se bloquea.
- Simplicidad: la implementación es más simple.
Desventajas:
- La condición puede no cumplirse: cuando el hilo señalado finalmente adquiere el mutex, la condición puede haber cambiado.
- Se necesita while: para manejar los despertares espurios y los cambios de condición.
Mesa es la semántica usada en la mayoría de los sistemas modernos (Java, C#, POSIX) porque es más eficiente.
9. Comparación
| Aspecto | Hoare | Mesa |
|---|---|---|
| Al hacer signal | El señalador se bloquea, el señalado se ejecuta. | El señalador continúa, el señalado espera el mutex. |
| Condición al ejecutar | Se cumple. | Puede no cumplirse. |
| Bucle | if es suficiente. | while es necesario. |
| Cambios de contexto | Más. | Menos. |
| Uso real | Poco usado. | Java, C#, POSIX. |
10. Monitores en lenguajes de programación
Los monitores se implementan de diferentes formas en distintos lenguajes:
| Lenguaje | Soporte de monitores | Semántica |
|---|---|---|
| Java | synchronized en métodos o bloques. wait(), notify(), notifyAll(). | Mesa |
| C# | lock statement. Monitor.Wait(), Monitor.Pulse(), Monitor.PulseAll(). | Mesa |
| Concurrent Pascal | Monitor nativo del lenguaje. | Hoare |
| Modula-3 | Monitor nativo con mutex y condition. | Mesa |
| C (pthreads) | No nativo. Se implementa con mutex + variables de condición. | Mesa |
Java y C# son los lenguajes más usados que soportan monitores nativamente. En C, los monitores se implementan manualmente con mutex y variables de condición.
11. Monitores en Java
En Java, todo objeto es un monitor potencial. El método synchronized adquiere el lock del objeto:
public class Cuenta {
private int saldo = 0;
public synchronized void depositar(int monto) {
saldo += monto;
notify(); /* señalar a un hilo que espera */
}
public synchronized void retirar(int monto) throws InterruptedException {
while (saldo < monto) {
wait(); /* esperar a que haya suficiente saldo */
}
saldo -= monto;
}
}
En Java, synchronized adquiere el lock del objeto. wait() libera el lock y bloquea. notify() despierta a un hilo. notifyAll() despierta a todos.
La semántica es Mesa: notify() no transfiere el lock inmediatamente. El hilo despertado debe esperar a que el lock se libere.
12. Monitores en C#
En C#, el statement lock adquiere el lock de un objeto:
public class Cuenta {
private int saldo = 0;
private readonly object lock = new object();
public void Depositar(int monto) {
lock (lock) {
saldo += monto;
Monitor.Pulse(lock); /* señalar */
}
}
public void Retirar(int monto) {
lock (lock) {
while (saldo < monto) {
Monitor.Wait(lock); /* esperar */
}
saldo -= monto;
}
}
}
En C#, lock es equivalente a Monitor.Enter/Exit. Monitor.Wait libera el lock y bloquea. Monitor.Pulse despierta a un hilo. Monitor.PulseAll despierta a todos.
13. Ventajas
- Exclusión mutua implícita: el programador no maneja locks manualmente.
- Encapsulación: los datos y las operaciones están encapsulados en el monitor.
- Menos errores: no se puede olvidar el unlock ni bloquear dos veces.
- Abstracción: el monitor es una abstracción de alto nivel.
14. Desventajas
- Menos flexible: no se puede bloquear un mutex y liberarlo en otra función.
- Menos control: no se puede usar trylock o timedlock fácilmente.
- Dependencia del lenguaje: solo está disponible en lenguajes que lo soportan.
- En C, se implementa manualmente: con mutex y variables de condición.
15. Simulador interactivo
Este simulador muestra cómo un monitor protege una cuenta bancaria:
Simulador · monitor (cuenta bancaria)
| Medida | Valor |
|---|---|
| Saldo inicial | 0 |
| Saldo esperado | |
| Saldo obtenido | |
| Diferencia |
16. Resumen
- Un monitor encapsula datos y operaciones con exclusión mutua implícita.
- El programador no maneja locks manualmente.
- Los monitores usan variables de condición (wait, signal, broadcast).
- Hay dos semánticas: Hoare (más segura, más costosa) y Mesa (más eficiente, requiere while).
- Java y C# soportan monitores nativamente.
- En C, se implementan con mutex y variables de condición.
- Los monitores reducen errores pero son menos flexibles que los mutex.
17. Preguntas de repaso
Intenta responder cada una antes de abrir la respuesta.
1. ¿Qué es un monitor?
Una abstracción que encapsula datos y operaciones con exclusión mutua implícita.
2. ¿Quién propuso el concepto de monitor?
C.A.R. Hoare y Per Brinch Hansen, en 1974.
3. ¿Qué es la exclusión mutua implícita?
El monitor garantiza automáticamente que solo un hilo ejecuta un método a la vez.
4. ¿Qué ventaja tiene la exclusión mutua implícita?
El programador no puede olvidar el unlock ni bloquear dos veces.
5. ¿Qué es la semántica de Hoare?
Al hacer signal, el señalador se bloquea y el señalado se ejecuta inmediatamente.
6. ¿Qué es la semántica de Mesa?
Al hacer signal, el señalador continúa y el señalado espera el mutex.
7. ¿Qué semántica usa Java?
Mesa.
8. ¿Qué semántica usa POSIX?
Mesa.
9. ¿Se pueden implementar monitores en C?
Sí, con mutex y variables de condición.
10. ¿Cuál es la desventaja principal de los monitores?
Son menos flexibles que los mutex (no se puede usar trylock fácilmente).
Para practicar
- Implementa un monitor en C con mutex y variables de condición.
- Escribe una cuenta bancaria como monitor en Java.
- Compara la semántica de Hoare y Mesa con un ejemplo.
- Explica por qué los monitores reducen errores.
- Investiga cómo se implementan los monitores en el núcleo de Linux.
- Implementa un monitor para una cola acotada.