Generalidades acerca de los hilos o procesos ligeros Un proceso es un programa en ejecución lo que significa que el proceso incluye los valores actuales del contador de programa, los registros de la CPU, las tablas de asignación de memoria y muchas otras cosas, en fin todo lo que permita que pueda ser interrumpido en un punto dado para después reanudar la ejecución. Toda la magia de interrumpir el proceso y poder continuar su ejecución posteriormente se apoya en la estructura de datos conocida como PCB (es un nombre genérico no tiene que llamarse así en todos los SO). Los hilos basan su idea en la misma concepción lo cual se refleja claramente en su nombre alternativo de procesos ligeros en contraposición de los procesos pesados que se crean cuando se ordena ejecutar un programa o se bifurca con una llamada al sistema para crear proceso (pesados) como la llamada al sistema fork de los sistemas operativo de la familia Unix. Los procesos pesados o simplemente procesos no comparten nada y la única forma de comunicarse entre sí es a través de paso de mensajes o por otras técnicas tales como las tuberías. El modelo de proceso posee un hilo simple de control de proceso mientras el modelo de hilo permite tener más de un hilo de control. Hilos Existen diversos programas para mejorar la velocidad con que se bajan los documentos de Internet, por ejemplo el Internet Download Manager, el Teleport (es específico para páginas Web), etc. La idea básica de esos programas es correr distintas instancias de ellos bajando diferentes partes del documento, cada instancia es un hilo que tiene la misma funcionalidad que las restantes pero actúa sobre datos diferentes. Los sistemas operativos modernos contienen funciones específicas para el trabajo con hilos. Un hilo es, básicamente, una unidad de utilización de la CPU y desde ese punto de vista tiene la misma estructura que un proceso y el SO debe usar un PCB para controlarlo. En la figura 1 se puede apreciar la diferencia entre un proceso y un hilo, observe que: • en el modelo de proceso el código los datos, la tabla de archivos abiertos los registros y la pila (en fin todo) son accedidos por un solo hilo de control de ejecución, • en el modelo de hilo existe un segmento de código, uno de datos y una tabla de archivos abiertos común pero cada hilo tiene sus propios registros y pila. código datos registros archivos pila código registros pila datos archivos registros pila registros pila hilo hilo Figura 1 Modelo de proceso y de hilo. Beneficio de los hilos Los beneficios de la programación multihilo se pueden ver desde cuatro aristas: 1. Mejor respuesta. Una aplicación multihilo permite que el proceso continúe ejecutando aún cuando alguna de sus partes está bloqueada o se encuentra resolviendo una operación muy lenta. Por ejemplo un browser multihilo puede permitir que el usuario interactúe con ella (a través de un hilo) mientras esta cargando una imagen (usando otro hilo). 2. Compartir recursos. Por defecto los hilos comparten la memoria y los recursos del proceso al cual pertenecen, lo que da por resultado que la aplicación pueda tener varios hilos en actividad dentro del mismo espacio de direcciones. 3. Economía. Asignar memoria y recursos para crear procesos es una actividad costosa. Debido a que los hilos comparten los recursos del proceso al cual pertenecen, resulta más económica su creación y el cambio de contexto (pasar el procesador de un hilo a otro). 4. Utilización de las arquitecturas de multiprocesadores. Los beneficios que da la programación con hilos puede incrementarse considerablemente en un sistema con más de un procesador ya que los hilos pueden ejecutar en paralelo de forma real. Muchos SO modernos usan el concepto de hilo, por ejemplo Solaris crea un conjunto de hilos en el kernel para controlar las interrupciones, Linux usa un hilo del kernel para controlar la cantidad de memoria, etc. Modelos multihilos. El soporte para hilo puede darse a nivel de usuario (hilos de usuario) o puede ser dado por el kernel (hilos de kernel). Los primeros se soportan arriba del kernel y por ende éste no lo apoya. Mientras los segundos son manejados directamente por el SO. Casi todos los SO modernos incluyen soporte para hilos a nivel de kernel, por ejemplo: Windows XP, Linux, Mac OS X, Solaris, entre otros. Debe existir una forma de establecer una relación entre los hilos de usuario y los hilos del kernel, seguidamente se detallan tres de ellas: 1. Modelo “muchos a uno”. En este modelo (figura 2) muchos hilos de nivel de usuario se mapean en un hilo kernel. La manipulación de los hilos la hace una biblioteca que reside en el espacio de usuario de forma que es eficiente, pero, si un hilo hace una llamada al sistema de bloqueo, el proceso entero se bloquea. Además debido a que solo un hilo puede acceder al kernel en un instante de tiempo dado, no se pueden correr procesos paralelos en sistemas con multiprocesadores. Hilo de usuario Hilo de kernel k Figura 2. Modelo muchos a uno. 2. Modelo “uno a uno”. Este modelo (figura 3) mapea cada hilo de usuario en un hilo del kernel, dando una mayor concurrencia que el modelo anterior y permite que un hilo corra cuando otro hace una llamada de bloqueo, además los hilos pueden correr en diferentes procesadores cuando se usan sobre un sistema de multiprocesamiento. El único problema es que la creación de un hilo de usuario necesita la creación de un hilo kernel. Linux y los SO Windows (95, 98, NT, 2000 y XP) implementan este modelo. Hilo de usuario k k k k Hilo de kernel Figura 3. Modelo uno a uno. 3. Modelo “muchos a muchos”. En este caso (figura 4) se multiplexan muchos hilos de nivel de usuario en una cantidad menor o igual de hilos kernel. La cantidad de hilos kernel puede especificarse para una aplicación particular o para una máquina particular. Los desarrolladores pueden crear tantos hilos como necesiten y el correspondiente hilo kernel puede ejecutar en una arquitectura paralela. Hilo de usuario k k k Hilo de kernel Figura 4. Modelo muchos a muchos. Hilos y bibliotecas de hilos Para clarificar la idea de los hilos, analice el siguiente ejemplo: • un grupo de estudiantes trabajan en un laboratorio de computación y todos hacen algún acceso a un servidor Web dado. Esta situación genera una buena cantidad de solicitudes que el servidor debe satisfacer, o ¿qué sucedería si una aplicación con un solo hilo de control fuera la responsable de satisfacer todas esas solicitudes? Queda claro que la cola de espera sería muy grande y la prestación de servicios muy mala, ahora imaginen el problema a nivel global, por ejemplo el acceso a Google que diariamente hacen muchas personas en el mundo . El modelo de hilo resuelve parte de ese problema al permitir que para cada petición se cree un hilo que “escucha” y atienda las solicitudes. Bibliotecas de hilos. Las bibliotecas de hilos proveen al programador una API para crear y manejar hilos, existen dos aproximaciones al problema. • la biblioteca está en el espacio de usuario y por tanto una invocación a una de sus funciones es una llamada a una función local dentro del espacio de memoria del usuario. • La biblioteca está implementada directamente en el kernel del SO y por tanto una llamada a una de sus funciones es una llamada al sistema en el espacio del kernel. Existen tres bibliotecas importantes que se apoyan en esta ideas: Pthreads, Win32 y Java. Hilos con Pthread. Pthread es una API para la creación y sincronización de hilos. En realidad es una especificación para el comportamiento de hilos (según el estándar POSIX IEEE 1003.1) y no una implementación, de manera que los diseñadores de sistemas operativos tienen que implementarla de la forma en que deseen. Existen muchos SO que la implantan, por ejemplo: Solaris, Linux, Mac OS X. Existen, además, implementaciones shareware disponibles y de dominio público para varias implantaciones de Windows. Observe, en el ejemplo que sigue a estos comentarios, el uso de la API Pthread para realizar una sumatoria. En un programa PThreads los hilos comienzan su ejecución en una función específica que en este caso es la función runner. Obsérvese que la variable sobre la que se realiza la suma -sum- es una variable compartida por todos los hilos. El programa comienza la ejecución, con un solo hilo de control, en la función main, en la línea pthread_create(&tid, &attr, runner, argv[1]); se crea el hilo, las parámetros que se le pasan a la función son el identificador del hilo (tid), su conjunto de atributos (attr), el nombre de la función que debe invocarse y el argumento que se le pasó al proceso original (argv[1]). En ese momento el proceso tiene dos hilos, el proceso padre y el que se acaba de crear que es el que realizará la suma. El hijo realizará la suma en la función runner y el padre ejecuta la función pthread_join para esperar por su hijo e imprimir el resultado de la suma. #include <pthread.h> #include <stdio.h> int sum; void *runner(void *param); /* variable compartida por los hilos */ /* el hilo */ int main(int argc, char *argv[ ]) { pthread_t tid; /* identificador del hilo */ pthread_attr_t attr; /*conjunto de atributos del hilo */ if (argc != 2) { fprintf(stderr, “uso: a.out <integer value>\n”); return -1; } if (atoi(argv[1] < 0) { fprintf(stderr, “%d debe ser >= 0\n”, atoi(argv[1]); return -1; } /* obtener los atributos por defecto */ pthread_attr_init(&attr); /* crear el hilo */ pthread_create(&tid, &attr, runner, argv[1]); /* esperar por el hilo */ pthread_join(tid, NULL); printf(“la suma es %d\n”, sum); } void *runner(void *param) { int, upper = ator(param); sum = 0; for(i = 1; i <=upper; i++) sum +=I; pthread.exit(0); } Hilos con Win32 La técnica usada en este caso es similar a la anterior en varios sentidos, observe el programa siguiente que ilustra las ideas. Al igual que que la versión anterior, en la que se uso Pthreads, los datos que se comparten por los hilos (Sum en este caso) se declaran globales (el tipo de dato DWORD es un entero sin signo de 32 bits). También se define la función Summatation() la que es usada por hilos separados, a la cual se le pasa un puntero a void que es definido en Win32 como LPVOID, los hilos que usan esta función ponen en la variable global Sum el valor de la suma desde 0 hasta el parámetro pasado a ella. Los hilos en Win32 se crean usando la función CreateThread(), la cual recibe un conjunto de atributos tales como: información de seguridad, la longitud de la pila y una bandera que puede iniciarse para indicar si el hilo va a comenzar en un estado suspendido. Como en el ejemplo no se ha indicado eso, el proceso es elegible por el planificador de periodo corto. Una vez que se crea el hilo de suma, el padre debe esperar la terminación del hijo para lo cual usa la función WaitForSingleObject(). El programa Phtread del ejemplo anterior usa para esos fines la sentencia pthread_join().. #include <Windows.h> #include <stdio.h> DWORD Sum; /* Datos compartidos por los hilos*/ DWORD WINAPI Summation(LPVOID Parm) { DWORD Upper = *(DWORD*) Param; for (DWORD i = 0; i <= Upper; i++) Sum += i; return 0; } int mai(int argc, char *argv[]) { DWORD ThreaID ; Handle ThreadHandle ; int param ; if(argc != 2) { fprintf(stderr, “Se necesita un entero\n“ ) ; return -1 } Param = atoi(argv[1]); if(Param < 0) { fprintf(stderr, “Se necesita un entero mayor o igual a cero\n) ; return -1¨ } //Crear el hilo ThreadHandle = CreateThread( NULL, //Atributos de seguridad por defecto Sumation, //Función hilo (thread) &Param, //Parámetros de la función hilo 0, //Banderas por defecto ThreadId, // Identificador de hilo retornado) if(ThreadHandle != NULL) //Espera por la terminación del hilo WaitForSingleObject(ThreadHandle, INFINITE); CloseHandle(ThreadHandle); printf(“la suma es %d\n”, sum); } Hilos en Java La tercera variante que mencionamos es usar los hilos desde un lenguaje de alto nivel que proporciona esa facilidad, el lenguaje Java posee una biblioteca con ese fin, no entraremos en detalles de ese caso. Las llamadas al sistema fork y exec en ambiente multihilo Cuando se hace una llamada al sistema fork, se crea una copia exacta (el hijo) del proceso que la invoca el cual pasa a competir individualmente por la CPU sin compartir nada con el proceso que lo creó (el padre). El hijo solo hereda un conjunto de recursos ya inicializados (archivos abiertos, los valores de las variables, etc.) pero todo es una copia del original en una zona de memoria diferente. Por otra parte una llamada al sistema exec, sustituye el código actual por el código de algún programa que se pasa como argumento. En algunos sistemas UNIX la llamada al sistema fork en un ambiente multihilo tiene dos versiones: • En el primer caso se duplican todos los hilos del proceso que la invoca. • En el segundo caso solo se duplica el hilo que la invocó.