Práctica 09 - ProgProcesosYServicios/Practicas-2 GitHub Wiki

Práctica 2.09: Evitando la espera activa

Las soluciones que hemos visto para la exclusión mutua hacen espera activa. Las llamadas & yield alivian el problema considerablemente en sistemas monoprocesador, pero en sistemas multiprocesador el yield, si hay procesadores libres no sirve para nada: el sistema Operativo volverá a planificar la misma hebra al tener recursos ociosos para ella.

Una solución es hacer uso de suspend() y resume(): una hebra se suspende a sí misma cuando no puede entrar en la sección crítica, y deja que sea la otra hebra la que la despierte cuando salga.

Vamos a añadir esta funcionalidad al algoritmo de Peterson que vimos previamente.

Haz una copia del proyecto de la práctica 2.8.

Como una hebra debe despertar a la hebra Opuesta) necesitamos conocerlas, más alla del número. Añade un atributo _threads que sea un array de dos hebras, y un método setThreads() que las reciba y las guarde.

public void setThreads(Thread t0, Thread t1) {

		_threads[0] = t0;
		_threads[1] = t1;

	} // setThreads

	protected Thread[] _threads = new Thread[2];

3.- Modifica el programa principal para que llame a setThreads()una vez que la haya creado:

public static void main(String[] args) throws InterruptedException {
		
...
		t1 = new Thread(racer, "Hebra0");
		t2 = new Thread(racer, "Hebra1");
		racer.setThreads(t1,  t2);
...
		
	} // main

4.- Modifica el método de entrada en la sección crítica para que en el bucle de espera activa se suspenda la hebra actual. Modifica también el método de salida para que se despierte a la hebra epuesta) incondicionalmente (un resume () de una hebra no suspendida es inocuo).

protected void entradaSeccionCritica(int numHebra) {

		_enSeccionCritica[numHebra].valor = true;

		int otraHebra = numHebra ^ 0x1;
		
		_turno = otraHebra;
		while(_enSeccionCritica[otraHebra].valor &&
		      (_turno == otraHebra))
			Thread.currentThread().suspend();
		
		// ¡Está libre!

	} // entradaSeccionCritica

protected void salidaSeccionCritica(int numHebra) {
		
		_enSeccionCritica[numHebra].valor = false;

		int otraHebra = numHebra ^ 0x1;
		// Un resume a una hebra no suspendida es inocuo. La
		// "despertamos" siempre.
		_threads[otraHebra].resume();
				
	} // salidaSeccionCritica

5.- Ejecuta el programa. ¿Funciona?

En un sistema multiprocesador hay muchas posibilidades de que no funcione y el programa se bloquee. Con el programa aparentemente congelado, vamos a ver que esta ocurriendo.

1.- Usa jps y jstack para comprobar el estado de las hebras Hebra0 y Hebra1.

2.- Comprueba que el estado es RUNNABLE. Sin embargo, el último método que ejecutaron fue el de suspensión.

3.- Utiliza ps aux -L para ver el estado de las hebras de la JVM a nivel del sistema. Comprueba que todas están suspendidas.

La visión por parte de la JVM del estado RUNNABLE es debido al uso de hebras nativas (y no hebras verdes). En cualquier caso, vemos que ambas están suspendidas: la solución planteada sufre de una condición de carrera.

El problema surge si una hebra está intentando entrar en la sección crítica, y está apunto de solicitar que se la suspenda porque ha visto que no puede. Es decir esta empezando a ejecutar la línea 10 del código anterior. Al mismo tiempo) la hebra que está en la sección crítica está saliendo y acaba de despertar a la hebra opuesta, algo que no ha tenido efecto: el resume() ha llegado un poco pronto.

La hebra que acaba de salir de la sección crítica vuelve a intentar entrar, pero ahora ve que el turno es de la opuesta, que quiere entrar. Por el funcionamiento del algoritmo de Peterson, esperaremos amablemente a que la otra hebra haga uso de su turno, por lo que dormimos hasta que nos despierte. Pero nunca lo hará. Hemos sufrido una condición de carrera por resume() perdido.