Algunos programas presentan una estructura que puede hacerles especialmente adecuados para entornos multihilo. Normalmente estos casos involucran operaciones que pueden ser solapadas. La utilización de múltiples hilos, puede conseguir un grado de paralelismo que incremente el rendimiento de un programa, e incluso hacer más fácil la escritura de su código. Algunos ejemplos son :
Algunas de estas técnicas pueden ser implementadas usando múltiples procesos. Los hilos son sin embargo más interesantes como solución porque :
Los hilos se crearon para permitir combinar el paralelismo con la ejecución secuencial y el bloqueo de llamadas al sistema. Las llamadas al sistema con bloqueo facilitan la programación, y el paralelismo obtenido mejora el rendimiento.
Consideremos el siguiente ejemplo de un servidor de archivos, en distintas implementaciones :
1) Servidor con varios hilos:
- El hilo servidor, lee las peticiones de trabajo del buzón del sistema.
- Examina la solicitud, y selecciona un hilo trabajador inactivo (bloqueado), al cual envía la solicitud mediante algún tipo de mensaje. Después el hilo servidor despierta al hilo trabajador dormido (por ejemplo, mediante un SIGNAL en el semáforo donde duerme).
- Cuando el hilo trabajador despierta, consulta la cache del bloque de memoria compartida entre todos los hilos, para verificar que puede dar servicio a la solicitud. Si no, envía una solicitud al disco para obtener el bloque de disco correspondiente (si se trata de una operación de lectura), y se duerme a la espera. Se llama al planificador, y se inicializa otro hilo, que podría ser el servidor para pedir más trabajo, o bien otro trabajador listo para realizar un trabajo. Pero veamos como se implementaría el servidor de archivos sin hilos.
2) Servidor con un único proceso (único hilo), en el cual el proceso servidor realiza todo el tratamiento cíclicamente:
- obtener una solicitud
- examinar el tipo de solicitud
- servir la solicitud, y volver al principio.
Mientras el proceso espera al disco, está inactivo y no procesa otras solicitudes. Si el servidor de archivos se ejecuta en una máquina dedicada, como suele ser habitual, la CPU estará inactiva, mientras se espera al disco, produciendo un menor número de solicitudes/sg. servidas. Así pues, los hilos aumentan el rendimiento de una forma importante, y sin embargo cada uno de ellos también se ejecuta de forma secuencial.
3) Servidor como máquina de estados finitos:
- obtener una solicitud
- examinar el tipo de solicitud
- Si se encuentra en la caché, perfecto, sino se envía un mensaje al disco, y en vez de bloquearse, se registra el estado de la solicitud actual en una tabla
- Después se consulta la tabla y se obtiene el siguiente mensaje, que puede ser una solicitud de un nuevo trabajo, o una respuesta del disco con respecto a una operación pendiente anterior.
- Si es un nuevo trabajo, se comienza su resolución. Y si es una respuesta del disco, se localiza la información necesaria en la tabla y se procesa la respuesta.
Como no se puede enviar un mensaje y bloquearse a la espera, no se puede utilizar RPC, así pues las primitivas deben ser del tipo send y receive sin bloqueo.
Sin embargo, en este diseño se pierde el modelo
"de proceso secuencial" que teníamos en los casos
anteriores. Necesitamos guardar el estado y restaurarlo en la
tabla para cada mensaje enviado o recibido. En el fondo lo que
estamos realizando es una simulación de varios hilos y
sus pilas de una manera complicada. El funcionamiento es el de
una máquina de estados finitos que obtiene un evento y
reacciona según el estado en el que se encuentre.
Los hilos, por tanto, mantienen la idea de procesos secuenciales que realizan llamadas al sistema con bloqueo (por ejemplo, RPC), y sin embargo permiten el paralelismo. Las llamadas al sistema con bloqueo facilitan la programación, y el paralelismo mejora el rendimiento del sistema. En el 2º caso, el servidor de un único hilo emplea llamadas con bloqueo, pero tiene un bajo rendimiento al no conseguir el paralelismo. El 3º caso, logra un mejor rendimiento mediante el paralelismo conseguido por la máquina de estados finitos, pero emplea llamadas sin bloqueo que dificultan la programación.
Los hilos también pueden ser muy útiles en la implementación no sólo de procesos servidores, sino de procesos clientes. Por ejemplo, si un cliente quiere copiar un archivo en varios servidores, puede hacer que un hilo se comunique con cada servidor.
También se pueden emplear los hilos en el manejo de señales, como por ejemplo las interrupciones del teclado: se puede dedicar un hilo a la espera de señales, en vez de que éstas interrumpan el proceso. Normalmente, el hilo se bloquea a la espera de señales, y cuando se produce una señal, despierta y la procesa. Esto puede eliminar la necesidad de interrupciones a nivel de usuario.
Otra razón de peso que justifica la utilización de hilos, y que no tiene relación con las RPCs o la comunicación, se basa en que muchas tareas son más fáciles de programar mediante procesos paralelos, ya que tienen una naturaleza inherentemente paralela. Como ejemplo encontramos el problema del productor-consumidor. Este problema es de naturaleza paralela, ya que de esta forma es más fácil su diseño, al margen de que en la realidad el productor y el consumidor se ejecuten o no de forma paralela. Además, dada la necesidad de compartir un buffer común, este problema se adapta perfectamente al caso de los hilos.
Como última justificación, en un sistema
multiprocesador, es posible que los hilos de un mismo proceso
se ejecuten realmente en paralelo, en varias CPU. Un programa
bien diseñado y que emplee hilos funcionará tanto
en una CPU con hilos compartidos, como en un verdadero multiprocesador,
lo que hace al diseño independiente de la implementación.
El empleo de los hilos en el desarrollo de aplicaciones
puede tener la recompensa de incrementar el rendimiento de la
aplicación. Un incremento en el rendimiento de la aplicación
puede significar una ventaja competitiva, sin embargo la robustez
de la aplicación y la portabilidad de la misma, han de
tenerse también en consideración. Desarrollar aplicaciones
robustas basadas en hilos es posible con un correcto entrenamiento
y con herramientas que ayuden al mantenimiento del programa. Desarrollar
aplicaciones portables será posible cuando la industria
se ciña al nuevo estándar POSIX 1003.1c.