Introducción a los Hilos IV (última)

Para cerra un poco este vistazo a este tema GIGANTE, vamos con algunos casos de estudios en la búsqueda de clarificar conceptos.


Casos de Estudio

A continuación se van a presentar tres casos de estudio, cada uno como un ejemplo de utilización de los hilos de nivel de usuario, de núcleo y una combinación de ambos. En cada caso se va a analizar tanto los aspectos de representación de los hilos como la planificación de los mismos en relación con los procesos que los soportan.

 


JVM

El primer caso de estudio se refiere a los hilos de nivel de usuario utilizando una biblioteca que da el soporte necesario para ello. Según se ha dicho anteriormente, existen varias posibilidades a la hora de elegir una biblioteca de hilos, de entre las cuales se ha seleccionado para su estudio a la Maquina Virtual Java, JVM, porque es de los pocos ca­sos, junto con Ada95 y Modula-3, en el que el soporte para la creación y la gestión de los hilos se da a nivel de lenguaje.

Todos los programas Java comprenden al menos un hilo de con­trol. Además Java permite la creación y la gestión de otros hilos en el programa. Además del programa Java conteniendo varios hilos, exis­ten otros hilos asíncronos que la JVM utiliza para realizar tareas del sistema como la gestión de la memoria o el control grafico. Un caso de hilo del sistema lo constituye el recogedor de basura.

Estados de los Hilos

Un hilo Java puede estar en cualquiera de los siguientes cuatro esta­dos (Figura 10):

  • Nuevo: un hilo estará en este estado cuando se cree con la sentencia new.
  • Ejecutable: la invocación del método start() reserva memoria pa­ra el nuevo hilo e invoca al método run() sobre el objeto hilo. En ese momento el hilo pasa al estado Ejecutable pudiendo ser elegido para ser ejecutado. JVM no distingue entre un hilo dispuesto a ser ejecutado del hilo que está en ejecución en un instante da­do, ya que este también se encuentra en el estado
  • Bloqueado: los hilos pasan a este estado cuando realizan una operación bloqueante, tal como una operación de E/S o la invocación a ciertos métodos como sleep() (que provoca que el hilo se suspenda por un tiempo determinado) o suspend() (que suspende al hilo hasta que la invocación del método resume() haga que el hilo bloqueado vuelva a ser ejecutable).
  • Terminado: un hilo se pasa a este estado cuando termina normal- mente o cuando se invoca al método stop() sobre él.

En la versión 2 de Java, se desaconseja utilizar los métodos suspend(), resume() y stop() pues pueden conducir a bloqueos mutuos en el caso de los dos primeros métodos, y porque se pueden producir estados inconsistentes en el tercer caso.

 


Windows 2000

En Windows 2000, W2K, la estructura original de los procesos y de los servicios que brinda el núcleo es relativamente simple y de propósito general. Las características más importantes de los procesos en W2K son las siguientes:

  • Los procesos se implementan como objetos.
  • Un proceso ejecutable puede tener un hilo o más.
  • Los objetos proceso e hilo tienen capacidades de sincronización.

Un proceso es una entidad correspondiente a un trabajo de usuario o a una aplicación, que dispone de sus propios recursos, tales como memoria y archivos. Un hilo es una unidad de trabajo que se puede expedir para su ejecución secuencial y que es interrumpible, de forma que el procesador puede pasar de un hilo a otro.

Estructura de los Procesos e Hilos

La forma en que un proceso se refiere a los recursos que controla es mediante referencias. El proceso almacena el apuntador a la lista de bloques de memoria a él asignados así como una tabla de objetos con los que está relacionado, entre ellos los hilos que soporta.

Cada proceso está representado por un objeto con una serie de atributos y de acciones o servicios. Los hilos también están representados por objetos, y algunos de sus atributos se parecen a los del proceso del que depende, por lo que sus valores suelen ser obtenidos a partir de  ellos. Uno de los atributos del objeto hilo es el contexto, que permite que los hilos puedan ser suspendidos y reanudados (Tabla 2).

W2K soporta concurrencia entre procesos basándose en que los hi­los de esos procesos se pueden ejecutar concurrentemente. Además, se pueden asignar varios hilos del mismo proceso a distintos procesa- dores si se está en un entorno multiprocesador.

Planificación de Hilos

Un hilo puede ser en alguno de los siguientes estados (Figura 11):

  • Listo: el hilo puede ser elegido para su ejecución.
  • Standby: el hilo ha sido elegido para ser el siguiente en ejecutarse en el procesador.
  • Ejecución: el hilo está siendo ejecutado.
  • Espera: un hilo pasa a este estado cuando se bloquea por un su- ceso (E/S), se realiza una espera voluntaria de sincronización o alguien suspende al hilo.
  • Transición: después de una espera el hilo pasa a este estado si está listo para ejecutar pero alguno de sus recursos no está disponible atm.
  • Terminado: un hilo llega a este estado cuando termina normal- mente o cuando su proceso padre ha terminado.

 


Solaris

Solaris implementa una arquitectura de hilos multinivel que aporta una gran flexibilidad. Para Solaris el proceso sigue el mismo modelo que los sistemas UNIX convencionales, incluyendo el espacio de direcciones de usuario, la pila y el bloque de control de proceso. Res- pecto a los hilos, Solaris considera tres tipos de hilos distintos:

  • Hilos a nivel de usuario: se implementan en el espacio de direcciones de un proceso por medio de la biblioteca de hilos y son invisibles para el sistema operativo.
  • Procesos ligeros: un proceso ligero (lightweight process, LWP) es una correspondencia entre un hilo a nivel de usuario y un hi­lo a nivel del núcleo. Cada LWP soporta uno o más HU y los hace corresponder con un HN. El núcleo planifica los LWP in- dependientemente y pueden ejecutarse en paralelo en entornos multiprocesador.
  • Hilos a nivel de núcleo: son las entidades básicas de planificación en cada uno de los procesadores del sistema.

La Figura 12 muestra la arquitectura de hilos multinivel de So­laris. Hay que observar que existe exactamente un hilo el núcleo por cada LWP. Un LWP es visible para la aplicación dentro del proceso, de modo que las estructuras de datos LWP están dentro de los espacios de direcciones de los procesos. A su vez, cada LWP está ligado a un único hilo del núcleo cuya estructura de datos se mantiene dentro del espacio de direcciones del núcleo. Esto permite una gran flexibilidad, parte de la cual está ilustrada en la figura anterior.

  • El proceso 1 está formado por un único HU ligado a un único LWP, lo que equivale al concepto de proceso UNIX clásico. Esta configuración será útil cuando la aplicación no necesite la concurrencia dentro del proceso.
  • El proceso 2 responde a una estrategia HU pura, donde un único LWP soporta a todos los HUs, por lo que solo uno de ellos puede ejecutarse en cada instante. Esta configuración resultara útil en aplicaciones que pueden programarse mejor por métodos concurrentes aunque no necesiten la ejecución de múltiples hilos en paralelo.
  • El proceso 3 hace corresponder a múltiples HUs sobre un número menor o igual de LWPs. Esto permite que la aplicación especifique el grado de paralelismo que soportara a nivel de núcleo.
  • El proceso 4 tiene sus HUs asociados permanentemente a los LWPs uno a uno. Esto permite que el paralelismo a nivel de núcleo sea accesible para la aplicación. Resulta útil cuando uno de los hilos tiene a bloquearse con frecuencia.
  • El proceso 5 muestra tanto una correspondencia de múltiples HUs sobre múltiples LWPs como la unión exclusiva de un HU con un LWP, estando además ese LWP ligado a un procesador concreto, lo que permite conseguir un tiempo de respuesta apropiado para aplicaciones de tiempo real.

Los hilos de usuario pueden estar ligados o no. Un hilo de usuario ligado está asociado permanentemente a un LWP y solo ese HU puede ejecutarse sobre ese LWP. Además existe la posibilidad de dedicar un procesador a un único LWP, con lo que se puede conseguir tiempos de respuesta tan rápidos como para soportar aplicaciones de tiempo real. Los hilos de usuario no ligados no están asociados permanentemente a un LWP, siendo multiplexados entre el conjunto de LWPs disponibles para ese proceso. Los hilos de usuario serán no ligados por defecto.

Esta variedad de configuraciones posibles entre HU, LWP e HN da al programador la posibilidad de aprovechar la concurrencia del modo más eficiente y apropiado para cada caso:

  • Algunos programas tienen un paralelismo lógico que si se sigue puede simplificar el código, aunque no necesitan paralelis­mo hardware. Así, se pueden implementar sobre múltiples HUs asentados todos sobre el mismo LWP. La ventaja de utilizar es­ta configuración es la eficiencia, ya que para crear, gestionar y destruir hilos no se necesita invocar al núcleo.
  • Si una aplicación tiene HUs que pueden bloquearse, como cuan­do se realiza una operación de E/S, le interesara contar con varios LWPs que soporten un número mayor o igual de HUs. Así, si se bloquea un HU (bloqueando el correspondiente LWP), el resto de los HUs puede continuar su ejecución sobre los demás LWPs.
  • La correspondencia uno a uno entre HUs y LWPs puede resultar interesante para algunas aplicaciones que necesitan paralelismo puro.
  • El modelo mixto, con HUs asociados a LWPs e HUs no asocia- dos (compartiendo varios LWPs) puede ser adecuado para dar mayor prioridad a ciertos HUs mientras que el resto comparte uno o varios LWPs. Además, si ligamos un LWP con un procesador (a través del correspondiente HN) podríamos conseguir una aplicación en el que un hilo responda en tiempo real.

Estados de los Hilos

Los estados de los HUs de usuario y LWPs van a depender de si el HU está o no ligado al LWP (Figura 13). Consideremos primero el caso de los hilos no ligados, aquellos que comparten varios LWPs. Un hilo no ligado puede estar en uno de los cuatro estados siguientes: ejecutable, activo, dormido o parado. Un HU en el estado activo está asignado a un LWP que se ejecuta mientras el HN subyacente se ejecute. Un hilo puede dejar el estado activo por varias razones:

  • Sincronización: el hilo utiliza una primitiva de sincronización con otros hilos. El sistema pasa el hilo al estado de dormido.
  • Suspensión: cualquier hilo, incluido el mismo, puede suspender a un hilo haciendo que pase al estado de parado. El hilo permanecerá en ese estado hasta que el otro hilo realice una petición de continuación.
  • Apropiación: cuando un hilo de mayor prioridad pasa a ejecutable expulsa al que está en ejecución para ocupar su lugar asociándose al LWP y ejecutándose.
  • Cesión: un hilo en ejecución puede invocar a la función thr_yield() para ver si existe otro hilo ejecutable con la misma prioridad que él, en cuyo caso le cederá el LWP, pasando a ejecutable. Si no encuentra el hilo adecuado seguira activo.

Cuando el hilo activo deja de serlo, la biblioteca de hilos selecciona a otro hilo no ligado en estado ejecutable y lo asocia con el LWP recién liberado. Cualquier transición desde estos estados devuelve al hilo al estado de ejecutable.

En cuanto a los estados de los LWPs no ligados mostrados también en la Figura 13, se pueden ver como una descripción del estado activo de un HU, ya que un HU solo tiene asociado un LWP cuando esta activo. Un hilo activo solo se ejecuta cuando su LWP está en estado de ejecución. Cuando un hilo realiza una llamada bloqueante, el LWP pasa al estado de bloqueado, manteniéndose ligado al HU, por lo que desde el punto de vista de la biblioteca de hilos el HU sigue activo.

La biblioteca de hilos ajusta dinámicamente el número de LWPs para cada proceso de forma que se aseguren las prestaciones del pro- ceso. Por ejemplo, si todos los LWPs de un proceso están bloqueados y existen otros hilos de usuario dispuestos a ejecutarse, la biblioteca de hilos crea automáticamente otro LWP para que se ejecuten los hi­los no bloqueados. Sin embargo, como los recursos ocupados por los LWPs y por los HNs resultan caros de mantener si no están siendo utilizados, la biblioteca de hilos mantiene un programa de envejecimiento, de modo que los borra si no han sido utilizados por ningún HU en un cierto intervalo de tiempo, 5 minutos por ejemplo.

El caso de los hilos ligados es algo distinto, de forma que si un HU pasa al estado dormido, obliga también al LWP a dejar de ejecutarse, bloqueándolo.

Por lo tanto, como resumen se puede decir que en el caso de hilos no ligados, el bloqueo de un HU afecta al LWP subyacente (pasa a estado bloqueado) y a los demás HUs que comparten el LWP ya que al permanecer el hilo en el estado activo, ningún otro hilo puede pasar a ese estado. Cualquier otro caso, ya sea adormecimiento o parada, no afecta al LWP, que seguirá en ejecución soportando a otro hilo que ocupa el lugar del inactivo. En el caso de los hilos ligados a LWP este sigue al pie de la letra los estados del hilo asociado, con la salvedad de que si el HU se duerme produce un bloqueo en el LWP.

Estructura de los Hilos

Las estructuras que soportan los hilos en Solaris son las siguientes:

  • Hilos de usuario: identificador del hilo, el conjunto de registros del usuario, la pila y la prioridad.
  • Procesos ligeros: conjunto de registros del usuario, la prioridad, la pila y la referencia al hilo del núcleo que lo soporta.
  • Hilos del núcleo: los registros del núcleo, un apuntador al LWP, e información sobre la prioridad y la planificación.

Cada proceso en Solaris tiene la información clásica del bloque de control del proceso, con un apuntador a los LWP asociados, según se puede observar en la Figura 14.

 


Linux

Para finalizar y completar el estudio sobre el modo en que los principales sistemas operativos abordan el tema de los hilos habría que estudiar el caso del sistema operativo Linux, dado el incremento en el número de implantaciones que se está dando. El único problema es que Linux no considera los hilos como tales. En Linux se crea un nuevo proceso copiando los atributos del proceso actual. Un nuevo proceso puede ser clonado para que comparta los recursos del actual, tales como archivos, gestores de señales o la memoria virtual. Cuan- do dos procesos comparten la memoria, operan en efecto como hilos dentro del mismo espacio, del mismo proceso. Sin embargo, no se manejan estructuras de datos para los hilos diferentes de las de los procesos, por lo que se puede argumentar que Linux no hace diferencias entre hilos y procesos.

 


Conclusiones

En esta lección se ha presentado y analizado el concepto de hilo par- tiendo del concepto de proceso destacando, entre otras, las siguientes conclusiones:

  • Los hilos surgen al disociar las características de propiedad de los recursos de la de ejecución de código que los procesos mantengan bajo una misma identidad.
  • Los sistemas multihilo son aquellos que pueden soportar la existencia de varios hilos por proceso. Este tipo de sistemas facilita tanto la sincronización entre procesos como la alternancia de estos en el procesador.
  • Se han descrito los estados posibles de los hilos como ejecución, listo y bloqueado y se han analizado las transiciones entre ellos.
  • Se han distinguido entre dos tipos de hilos: los de usuario y los del núcleo, basándose en el criterio de donde se realiza la gestión de los hilos. A partir de estos dos tipos de hilos se pueden tener tres casos de sistemas: hilos a nivel de usuario, hilos a ni­vel del núcleo y sistemas mixtos. En cada caso se han descrito las ventajas e inconvenientes de su utilización.
  • Se han presentado las principales características de la gestión de hilos en tres casos distintos: la maquina virtual Java, Windows 2000 y Solaris; resultando de este análisis que los hilos se implementan los distintos sistemas reales de muy diversas maneras.

Final de esta serie de Posts. ?

Dejá un comentario