Las aplicaciones informáticas pueden ser de muchos tipos, desde procesadores de texto hasta procesadores de transacciones (bases de datos), simuladores de cálculo intensivo, etc.
Las aplicaciones están formadas de uno o más programas. Los programas constan de código para la computadora donde se ejecutarán. El código es generalmente ejecutado de forma secuencial. Normalmente, un "programa hilado" (threaded program, programa construido mediante hilos) tiene el potencial de incrementar el rendimiento total de la aplicación en cuanto a productividad y tiempo de respuesta mediante ejecución de código asíncrono y paralelo.
La ejecución de código paralelo se realiza mediante la ejecución de dos o más partes de un programa en dos o más procesadores en un instante dado. La ejecución asíncrona se puede realizar conmutando la ejecución del segmento de código actual que se bloquea por alguna razón, a otro segmento. Los hilos permiten al programador emplear estas características y muchas otras.
Otros beneficios de los hilos están relacionados con los recursos compartidos y el escaso tiempo empleado en su creación, terminación y cambio de contexto. Todo esto contribuye a incrementar el rendimiento de la aplicación así como conservar los recursos del sistema.
Cuando un programa se activa en el sistema, es decir está en ejecución o es candidato para la ejecución, se le conoce como un proceso. Cuando un proceso se encuentra activo, se le asignan una serie de recursos del sistema operativo para gestionarle, entre ellos una entrada en la tabla de procesos, un área de usuario, un contexto de registros único, memoria virtual, etc. La mayoría de los sistemas operativos sólo soportan procesos. Los procesos pueden ser vistos como una entidad "hilo simple", es decir, ejecutan sólo un flujo de instrucciones de una forma secuencial.
Los procesos son planificados para ejecución una y otra vez hasta que finalizan, es decir, el programa termina su tarea. Cuando es planificado, un proceso se ejecutará en un procesador durante un intervalo de tiempo llamado quantum de tiempo. Un quantum es simplemente la cantidad de tiempo que el proceso tiene permitido para su ejecución antes de que el sistema operativo compruebe si existe un proceso de mayor prioridad listo para la ejecución. Un nuevo proceso es planificado para ejecución en lugar del proceso actual cuando :
Siendo un hilo simple, una aplicación puede ejecutarse sólo en un procesador en un instante dado y sólo puede ejecutarse de forma secuencial. Una forma de incrementar la velocidad de ejecución de un programa secuencial sería dividir el trabajo entre múltiples procesadores. Es aquí donde los hilos resultan útiles.
En los sistemas operativos tradicionales, cada proceso tiene un espacio de direcciones y un único hilo de control (contador de programa). Es lo que normalmente se entiende por proceso. Sin embargo, existen ocasiones en las que sería conveniente que dos procesos trabajasen de forma concurrente pero con una serie de datos comunes. Este problema es difícil de resolver normalmente, ya que los procesos son independientes y la comunicación de los datos debe realizarse mediante algún mecanismo de comunicación entre procesos (IPC), como pueden ser: semáforos, memoria compartida, paso de mensajes, etc. Pero parece claro que se introduce una cierta complejidad en la resolución de este tipo de problemas.
Una solución que parece razonable consistiría
en que los "procesos" compartiesen el espacio de direcciones
(la memoria), de esta forma no sería necesario su intercomunicación,
ya que todos tendrían acceso a la misma zona de memoria.
Se plantean sin embargo otros problemas, como es el acceso concurrente
a una misma posición de memoria, evitando los problemas
de inconsistencia de datos.
El significado exacto del término thread no está acordado. Uno de los usos más habituales denota a procesos ligeros (con flujo de control secuencial) que comparten un espacio de direcciones y algunos otros recursos, y para los cuales el tiempo empleado en el cambio de contexto es mucho menor que el empleado en los procesos pesados (procesos soportados por el kernel del sistema operativo).
Los hilos son flujos de control independientes dentro de un mismo proceso que comparten datos globales (variables globales, ficheros, etc.), pero poseen una pila, variables locales y contador de programa, propios. Se les suele llamar "procesos de peso ligero" porque su contexto es menor que el contexto de un proceso. Por lo tanto, los cambios de contexto entre hilos son menos costosos que los cambios de contexto entre procesos. Además, los hilos son un modelo adecuado para explotar el paralelismo en un entorno multiprocesador de memoria compartida.
La gestión de procesos es uno de los aspectos claves en el diseño de un sistema operativo. De la optimización de su funcionamiento depende en gran medida la buena utilización del sistema y sus recursos.
La noción de hilo, como flujo de control secuencial, se remonta al menos, al año 1965 [OSU96], con el sistema de tiempo compartido de Berkeley. Sólo que en aquel tiempo no fueron llamados hilos, sino procesos. Los procesos interactuaban a través de variables compartidas, semáforos, o mecanismos similares. Max Smith realizó un prototipo de implementación de hilos en Multics alrededor de 1970; usaba pilas múltiples en un proceso pesado simple para soportar compilaciones en background.
Sin embargo, el progenitor más importante de los hilos fue el lenguaje de programación PL/I, hacia el año 1965. El lenguaje, según fue definido por IBM, proporcionaba una instrucción del tipo CALL XXX (A, B) TASK; construcción que bifurcaba generando un hilo para XXX. No se sabe si algún compilador de IBM implementó esta característica, pero fue estudiado exhaustivamente mientras se estaba diseñando Multics; se comprobó que la llamada TASK como estaba definida no mapeaba en procesos, ya que no existía protección entre los hilos de control. Entonces Multics tomó una dirección diferente, y la característica TASK fue eliminada de PL/I por IBM.
Después vino UNIX, a principios de 1970. La noción UNIX de 'proceso' consistía en un hilo de control secuencial más un espacio de direcciones virtuales (esta noción derivó directamente del proceso de diseño de Multics). Así pues 'procesos', en el sentido de UNIX, son "máquinas pesadas". Ya que no pueden compartir memoria (cada proceso tiene su propio espacio de direcciones), interactuan a través de tuberías, señales, etc. La memoria compartida fue añadida a UNIX mucho más tarde.
Después de algún tiempo, los usuarios de UNIX comenzaron a echar en falta los viejos procesos que podían compartir memoria. Esto dio lugar a la "invención" de los hilos: procesos al "viejo estilo" que compartían el espacio de direcciones de un simple proceso UNIX. Se les llamó, entonces, procesos ligeros en contraste con los procesos pesados UNIX. Esta distinción se remonta a finales de los 70's y principios de los 80's, cuando comenzaron los desarrollos de los primeros microkernels: Thoth (precursor del V-Kernel y QNX), Amoeba, Chorus, la familia RIG-Accent-Mach, etc.
En relación con los paquetes de hilos, C Threads fue una de las implementaciones iniciales de hilos, nacida como una extensión del lenguaje C basada en corrutinas. Una implementación sencilla fue empleada por Cooper [COO88] como herramienta de enseñanza. Esta implementación original no gestionaba señales por hilo, y sólo soportaba planificación no preemptiva.
El primer sistema operativo comercial que implementó hilos fue Mach. Cooper construyó una implementación de C Threads basada en los hilos Mach que soportaba planificación preemptiva.
También existe una implementación anterior, realizada en Brown University [DOE87], de una biblioteca de hilos con planificación preemptiva, soporte de diversas arquitecturas (incluso multiprocesadores) y gestión de señales asíncronas.
Posteriormente, algunos sistemas operativos comerciales, como LynxOS o SunOS, implementaron alguno de los diversos borradores del estándar POSIX Threads, empleando un método mixto a dos niveles, basado en una biblioteca de nivel usuario y soporte kernel de hilos. Otros sistemas operativos, como Chorus implementaron la mayor parte de la funcionalidad en el kernel, debido a sus requisitos de diseño.
Se pueden distinguir dos tipos de implementaciones de los hilos : hilos a nivel de usuario y los hilos a nivel de kernel.
Un hilo a nivel de usuario mantiene todo su estado en el espacio de usuario. Como consecuencia de ello, el hilo no utiliza recursos del kernel para su gestión, y se puede conmutar entre hilos sin cambiar el espacio de direcciones. Como desventaja, los hilos a nivel de usuario no se pueden ejecutar mientras el kernel está ocupado, por ejemplo, con paginación o E/S, ya que esto necesitaría algún conocimiento y participación por parte del kernel.
Es posible combinar ambos métodos, como se hace en SunOS 5.x (Solaris 2.x). Aquí, uno o más procesos ligeros (light weight processes - LWPs) se ejecutan en multitarea mediante uno o más hilos de nivel usuario, que son implementados usando librerías en el espacio de usuario.
Algunas razones que caracterizan las implementaciones de hilos, y que determinan el uso que puede tener un paquete de hilos, son:
Algunos nuevos sistemas (QNX y Plan 9, por ejemplo) consideran que los hilos "resuelven el síntoma, pero no el problema". En estos sistemas se considera que en vez de usar hilos, ya que el tiempo de cambio de contexto del SO es demasiado lento, una mejor aproximación, es mejorar el SO en sí.
Parece irónico, que ahora que los SO para ordenadores de sobremesa incluyen multitarea en modo protegido, el modelo de programación de moda consiste en múltiples hilos ejecutándose en un espacio de direcciones común, haciendo el proceso de depuración más difícil, e incluso haciendo más difícil la generación de código fiable.
Con la ventaja de tener un rápido cambio de
contexto, y existiendo servicios del SO, como la reserva explícita
de memoria compartida entre un equipo de procesos cooperando,
se puede crear un entorno de hilos, sin tener los problemas
que se establecerían en un espacio de memoria totalmente
compartida.