Aplicaciones Multihilo en Delphi. Los problemas del multihilo

Introducción

En los dos artículos anteriores hemos visto una pequeña introducción a la programación multihilo en delphi así como a las funciones de sincronización que Delphi proprociona. Estas funciones de sincronización y el mero hecho de programar usando varios hilos tiene aparejados una serie de problemas a los que deberemos hacer frente si queremos que nuestra aplicación funcione correctamente y sin problemas.

Condición de carrera y exclusión mútua

Una condición de carrera es una situación indeseable en programación y que consiste en que el correcto funcionamiento del programa (o de un segmento del código del programa) depende del orden de ejecución de las tareas.

El problema principal de las condiciones de carrera es que son dificiles de identificar y encontrar y, dado que dos ejecuciones de un mismo programa pueden tener dos planificaciones completamente distintas para sus hilos, muy dificiles de reproducir.

El uso de primitivas de sincronización para todos los datos compartidos nos ha permitido en todo momento evitar situaciones como la siguiente:

  procedure Tarea1.Execute;
  begin
    while not Self.Terminated
    begin
      sleep(1000)
      i := i + 1;
    end;
  end;

  procedure Tarea2.Execute;
  begin
    // Imprimir i si i es par
   while not Self.Terminated
   begin
     sleep(500); // Dormir durante un segundo
     if EsPar(i) then // Si es par
       Memo1.Lines.Add('Valor ' + IntToStr(i));
   end;
  end;

Si ejecutaramos este ejemplo obtendríamos lo siguiente:

Ejecución Esperada Ejecución Real
Valor 0 Valor 0
Valor 2 Valor 2
Valor 4 Valor 4
Valor 6 Valor 7
Valor 8 Valor 8

Este comportamiento es debido a que, como ya vimos en los artículos anteriores, hay una sección crítica en el acceso a la variable global de forma que se produce una situación de carrera, dependiendo del orden en que se ejecuten los hilos, esto es, de la planificación que realice el planificador obtendremos unos resultados o otros siendo solo un subconjunto de ellos correctos.

Este problema se soluciona garantizando exclusión mutua entre las tareas que acceden a la sección que puede producir el problema (sección crítica). Sin embargo el uso de exclusión mutua va a dar lugar a otro tipo de problemas.

Interbloqueos

Un interbloqueo es una situación en la cual varias tareas se bloquean entre si permanentemente al intentar adquirir una serie de recursos. Un ejemplo típico de interbloqueo se produce cuando dos tareas intentan acceder a dos secciones críticas en ordenes distintos de forma que la primera tarea adquiere la primera sección crítica mientras la segunda adquiere la segunda sección critica, acto seguido ambas tareas intentan adquirir la otra sección crítica quedandose ambas bloqueadas, esperando a que la otra tarea libere la sección:

procedure Tarea1.Execute;
begin
  critical1.Acquire;
  ciritcal2.Acquire;
  HacerAlgo();
  critical2.Release;
  critical1.Release;
end;

procedure Tarea2.Execute;
begin
  critical2.Acquire;
  critical1.Acquire;
  HacerAlgo();
  critical1.Release;
  critical2.Release;
end;

En el ejemplo anterior la situación conflictiva se produce cuando:

  • La Tarea1 ejecuta y adquiere el critical1.
  • Simultaneamente la Tarea2 adquiere el critical2.
  • La Tarea1 intenta adquirir el critical2 y se queda bloqueada esperando a que la Tarea2 lo libere.
  • A su vez la Tarea2 intenta adquirir el critical1 y se bloquea esperando a que la Tarea1 lo libere.

De esta forma nos encontramos con dos Tareas que están esperando la una por la otra y que permanecerán (inter)bloqueadas para siempre.

No hay forma sencilla de prevenir el interbloqueo aparte de la inspección visual de las zonas de código que contiene los elementos de sincronización propensos a producir interbloqueos. La mayor parte los interbloqueos básicos (los más obvios) saltarán la primera vez que ejecutemos el programa pero aquellos interbloqueos que dependan de una determinada secuencia de ejecución y que en otro caso no se den serán más dificiles de detectar y encontrar.

Inanición y Espera acotada

Se dice que existe una situación de inanición cuando se da el caso en el que algún hilo pueda intentar obtener un recurso y sin producirse un interbloqueo nunca conseguirlo.

Este caso se produce cuando un recurso, pese a estar libre de interbloqueos, no cumple el requisito de espera acotada en el acceso al recurso, es decir, el hilo que intenta acceder al recurso puede ser superado una y otra vez por, por ejemplo, hilos más rapidos.

Veamos un ejemplo típico, un pipe con prioridad. Cada hilo que accede al pipe tiene una determinada prioridad de forma que se atiende primero a los hilos con mayor prioridad. Ahora supongamos que un determinado hilo con prioridad 2 entra al pipe y lo encuentra ocupado por un hilo de prioridad 4 y por tanto se queda a la espera. Mientras está esperando llega otro hilo de prioridad 3 por lo que ese hilo entra antes que él, mientras está esperando a ese último hilo llega de nuevo otro con prioridad 4 que también entrará antes que él ... y asi para siempre.

En este caso el hilo no se encuentra en una situación de interbloqueo, el resto de hilos entran y salen del pipe consiguiendo el acceso al recurso pero la espera de nuestro hilo no está acotada (puede ser infinita).

Para el caso anterior la solución más sencilla es establecer un sistema de promoción de la prioridad del hilo en función del tiempo que lleve esperando. El problema de la inanición consiste en que es dificil de detectar puesto que, en muchos casos se trata tan solo de una prioridad teórica que es dificil (pero no imposible) que se de en la práctica por lo que la mayor parte de las veces que ejecutamos nuestro programa este funciona perfectamente (hasta el día menos pensado que de repente una de las tareas se cuelga). Además la gran mayoría de las funciones de sincronización de windows (aunque no las de delphi) permiten especificar un determinado tiempo de espera (timeout) tras el cual el hilo retorna (informando de que no ha podido adquirir el recurso) lo cual nos permite evitar interbloqueos.

8
Average: 8 (2 votes)
Your rating: None