Skip to content

1 Introducción a la Gestión de Hilos

Introducción

En el mundo de los ordenadores, cuando hablamos de concurrencia nos referimos a una serie de tareas que se ejecutan simultáneamente en un ordenador. Esta simultaneidad puede ser real, si el ordenador tiene más de un procesador o un procesador con varios núcleos, o simulada, si sólo dispone de un único núcleo de procesamiento.

Proceso

Un proceso es una unidad de reserva de memoria y de protección, desde el punto de vista del sistema operativo.

Todos los sistemas operativos modernos permiten la ejecución de tareas concurrentes. Por ejemplo, podemos leer nuestros emails mientras escuchamos música o leemos las noticias en una página web. Podríamos decir que este tipo de concurrencia corresponde a una concurrencia a nivel de proceso.

Pero dentro de un proceso también podemos tener varias tareas simultáneas que se ejecutan dentro de él, a las que llamamos hilos o hebras (threads en inglés).

Procesos e hilos

Figura 1 - Procesos e hilos

Hilos

Los hilos son unidades de ejecución de conjuntos de instrucciones, de manera que dichos conjuntos pueden ejecutarse de forma concurrente en núcleos de procesamiento (ver).

Los procesos y los hilos mantienen una serie de diferencias importantes:

  • Los hilos comparten el espacio de memoria del usuario, incluyendo datos, código y recursos, mientras que los procesos generalmente disponen de espacios de memoria independientes e interactúan a través de mecanismos de sincronización dados por el sistema. Cada hilo dispone de su propio contador de programa, estado de los registros de la CPU y pila, para poder guardar el punto actual de ejecución.
  • El cambio de estado de un hilo es mucho menos costoso que el de un proceso.
  • Los hilos no pueden ejecutarse por sí solos, necesitan estar contenidos en un proceso padre. Cuando se crea un proceso (cuando se lanza la ejecución de un programa), se crea dentro de él un hilo principal, que puede, a su vez, crear tantos hilos como desee, que pasarán a ejecutarse contenidos dentro del proceso.

Los hilos son usados normalmente para, dentro de un proceso, llevar a cabo distintas tareas simultáneamente, o para realizar tareas costosas en tiempo sin detener el resto del programa.

Por ejemplo en un proceso correspondiente a un editor de textos, podemos tener un hilo encargado de estar llevando a cabo la comprobación ortográfica del texto, o guardando automáticamente en documento en segundo plano cada cierto tiempo. Otro ejemplo típico es el de un servidor web que cada vez que recibe una petición crea uno nuevo para atenderla, permitiendo seguir recibiendo peticiones en el hilo principal.

En los entornos gráficos el hilo principal de una aplicación corresponde normalmente a lo que se conoce como hilo de la interfaz de usuario, cuya misión es actualizar la interfaz cada vez que haya cambios y detectar las interacciones del usuario. En estos sistemas es muy importante, que dicho hilo no quede bloqueado por una operación costosa, dado que el usuario tendrá la sensación de que la aplicación no responde, dado que el hilo principal no podrá atender a las interacciones del usuario. Por este motivo, en estos entornos, todas las tareas que no sean prácticamente inmediatas deben llevarse a cabo en un hilo secundario o hilo trabajador, de manera que puedan quedar bloqueados sin afectar a la capacidad de respuesta del hilo de la UI.

Los hilos aportan una serie de beneficios:

  • Mejoran el rendimiento de la aplicación en hardware con varios núcleos de procesamiento, ya que se ejecutan en paralelo.
  • Incrementan el rendimiento de la aplicación, ya que una llamada de E/S solamente bloqueará el hilo en el que se realiza.
  • Aumentan la capacidad de respuesta de la aplicación, ya que un hilo de la UI puede estar atendiendo siempre las peticiones de usuario mientras otros hilos realizan otras tareas más costosas. El resultado es que el usuario no adquiere la sensación de que el programa esté "colgado".
  • La comunicación entre hilos se puede realizar mediante variables, ya que todos los hilos del proceso comparten la memoria y los recursos asignados a éste. Sin embargo, este tipo de comunicación hace necesario el establecimiento de métodos de sincronización entre los hilos. En otros casos se lleva a cabo la comunicación entre hilos a través de paso de mensajes, como en el caso de entornos con un hilo dedicado a la IU.
  • La creación de nuevos hilos no conlleva la reserva adicional de memoria, ya que utilizan la del proceso al que pertenecen. Sin embargo, sí que necesitan una pila específica para cada uno de ellos.

Comunicación entre hilos

La comunicación entre hilos de un mismo proceso se puede llevar a cabo de dos maneras distintas:

Comunicación mediante objetos compartidos en memoria

Figura 2 - Comunicación mediante objetos compartidos en memoria

A través de objetos compartidos en memoria

Así, un hilo puede escribir en un objeto y otro hilo puede leer dicho objeto.

Como consecuencia del acceso concurrente desde distintos hilos, será necesario establecer mecanismos de sincronización de acceso a datos.

Paso de mensajes

Figura 3 - Paso de mensajes

A través de paso de mensajes entre hilos

El hilo posee una cola de mensajes accesible desde ambos hilos.

Así, un hilo enviará un mensaje a la cola de mensajes, que se insertará por un extremo, y otro hilo irá leyendo los mensajes de la cola, que los irá extrayendo del otro extremo.

Este es el tipo de comunicación que se utiliza habitualmente en los sistemas en lo que el hilo principal corresponde al hilo de la interfaz de usuario, que es el único que puede actualizarla. Es el caso por ejemplo de Android.

Estados de un hilo

A lo largo de su ejecución, un hilo puede pasar por los siguientes estados:

Diagrama de estados de un hilo

Figura 4 - Diagrama de estados de un hilo

  • Nuevo / Naciendo (new): Se ha creado el objeto correspondiente al hilo (se ha hecho el new), pero aún NO se ha iniciado su ejecución.
  • Listo (runnable): El hilo está preparado para ser ejecutado, pero el planificador del sistema operativo no le ha asignado todavía el procesador para ejecutarse.
  • Corriendo / En ejecución (running): El hilo está ejecutándose en el procesador.
  • Bloqueado (blocked): El hilo está bloqueado al intentar acceder a un recurso vigilado por un objeto monitor que no está libre.
  • Suspendido (waiting): El hilo ha sido suspendido mediante código en espera de que se le notifique desde código que puede continuar.
  • Suspendido por tiempo (timed waiting): El hilo ha sido suspendido mediante código en espera de que pase un determinado tiempo.
  • Terminado (terminated): El hilo ha finalizado su ejecución ya sea naturalmente, al finalizar su método run(), o abruptamente, por alguna excepción que se haya producido en dicho método. El entorno de ejecución de Java puede liberar los recursos asociados al hilo, como por ejemplo su memoria