Cargando aplicación...
Preparando tu experiencia meskeIA
Ejecuta los hilos paso a paso y controla el interleaving: observa los semáforos, provoca una condición de carrera en la sección crítica y reproduce el interbloqueo de los filósofos comensales.
Semáforos, exclusión mutua, condiciones de carrera e interbloqueo
| Concepto | Qué es | Problema que resuelve / causa |
|---|---|---|
| Sección crítica | Trozo de código que accede a un recurso compartido | Debe ejecutarse en exclusión mutua para evitar carreras |
| Mutex | Semáforo binario (valor 0 o 1) | Garantiza que solo un hilo entre a la sección crítica |
| Semáforo contador | Entero ≥ 0 con cola de espera | Controla acceso a N recursos (buffers, conexiones…) |
| Condición de carrera | Resultado dependiente del interleaving | Aparece sin sincronización; datos corruptos |
| Interbloqueo (deadlock) | Espera circular permanente | Nadie avanza; requiere romper una condición de Coffman |
| Inanición (starvation) | Un hilo nunca obtiene el recurso | Distinto del deadlock: el sistema avanza, pero ese hilo no |
Un mutex es un semáforo binario pensado para exclusión mutua: lo bloquea y lo libera el mismo hilo. Un semáforo contador puede tener un valor mayor que 1 y lo manipulan hilos distintos (un hilo hace wait y otro hace signal), como en el productor-consumidor.
En el simulador, los semáforos binarios muestran “en manos de” y los contadores muestran “contador”.
Porque solo aparece con ciertos interleavings, que dependen del planificador del sistema operativo y de la carga de la máquina. El mismo programa puede funcionar mil veces y fallar la siguiente, lo que hace que el error sea casi imposible de reproducir a voluntad.
Por eso aquí ejecutas los hilos a mano: puedes forzar justo el interleaving que rompe todo.
Son cuatro y deben darse a la vez: exclusión mutua (un recurso no se comparte), retención y espera (un hilo retiene recursos mientras pide otros), no apropiación (no se le pueden quitar a la fuerza) y espera circular (una cadena cerrada de esperas).
Romper cualquiera de las cuatro evita el deadlock. Lo más práctico suele ser romper la espera circular.
No. En un deadlock ningún hilo del grupo avanza nunca. En la inanición (starvation) el sistema sí progresa, pero un hilo concreto nunca consigue el recurso porque otros se le adelantan siempre. La inanición se combate con políticas justas (colas FIFO, prioridades).
Un semáforo con cola FIFO, como el de este simulador, evita la inanición en la espera.
Cada filósofo necesita dos tenedores. Si todos cogen primero el de su izquierda, los cinco tenedores quedan ocupados y ninguno puede coger el de la derecha: espera circular perfecta. Avanza a todos los filósofos a la vez en el simulador para verlo.
Activa “romper la simetría” y comprueba que ya no ocurre.
El buffer se llena y el semáforo de huecos vacíos llega a cero. Entonces el productor se bloquea en su wait hasta que un consumidor saque un dato y haga signal. Así nunca se escribe fuera del buffer ni se pierden datos, por mucha diferencia de velocidad que haya.
Ejecuta solo los productores y verás cómo se bloquean al llenar el buffer.
Busca qué dato leen y escriben varios hilos: una variable global, una estructura, un fichero. Ese acceso es tu sección crítica.
Antes de tocar el recurso, el hilo hace wait sobre un mutex inicializado a 1. Si otro hilo ya está dentro, el mutex vale 0 y este se bloquea.
Mantén la sección crítica lo más corta posible: cuanto más tiempo retengas el mutex, más esperan los demás y peor escala el sistema.
Al salir, haz signal. Si había hilos esperando, uno de ellos se despierta y entra; si no, el mutex vuelve a 1.
Si un hilo necesita varios mutex, adquiérelos siempre en el mismo orden en todos los hilos. Así evitas la espera circular.
Retén el mutex el menor tiempo posible. No hagas dentro operaciones lentas como E/S.
Si tomas varios cerrojos, hazlo siempre en el mismo orden global. Rompe la espera circular.
Colas concurrentes, monitores o canales suelen ser más seguros que semáforos a mano.
Esperar un cerrojo con tiempo límite convierte un deadlock en un error tratable.
No confíes en que “casi siempre va bien”. Fuerza el peor orden posible, como aquí.
El código sin datos compartidos no tiene carreras. Inmutabilidad y mensajes ayudan mucho.