1.
Introducción
En
las primeras implementaciones de la computación, la programación manejaba
procesos en un solo hilo de ejecución. Los programas se creaban en base a
tarjetas perforadas o cintas, por lo cual su lectura era totalmente secuencial,
en otras palabras, también llamada por lotes basado por el hecho de “el primero
que llega es el primero en ser atendido”, cuando era ejecutado el programa, la
computadora tenia uso exclusivo para él.
El
concepto más importante en cualquier sistema operativo es el de proceso, una
abstracción de un programa en ejecución, es por ello que todos lo demás depende
de este concepto. Los procesos son una de las abstracciones más antiguas e
importantes que proporcionan los sistemas operativos, proporcionan la capacidad
de operar concurrentemente.
El
concepto de múltiples hilos de ejecución aparece con los sistemas de tiempo
compartido, donde más de una persona podía conectarse a una computadora central
a la vez. Con esto nacen los conceptos de proceso e hilo (thread). Los primeros sistemas operativos funcionaban
con un único hilo de ejecución, por mencionar un ejemplo, DOS. Pero con la
llegada actual de aplicaciones gráficas y en red, el uso de los sistemas
operativos multiproceso y multihilo se volvieron algo común. Con ello se ha
obtenido un mayor uso y rendimiento.
En
este presente reporte se hablará sobre los conceptos más relevantes de procesos
e hilos, haciendo mención sobre la programación con hilos, funcionamiento de
hilos y un pequeño programa en donde se puede apreciar la programación de hilos
y el funcionamiento.
2.
Marco teórico
2.1. Proceso
Un
proceso se puede definir como un programa en ejecución en la computadora o en
algún otro dispositivo que contenga un sistema operativo, compartiendo el
microprocesador, la memoria y otros recursos del sistema, con otros programas.
El proceso es invocado por un código ejecutable, además, posee una única
existencia. Cada proceso se ejecuta de manera aislada los otros, cada proceso
se ejecuta de manera aislada a los otros. Los procesos ejecutan módulos de
código, los cuales pueden ser independientes o no.
2.1.1. Creación de un
proceso
Existen
cuatro eventos principales que provocan la creación de procesos:
·
El arranque del
sistema.
·
La ejecución, desde
un proceso, de una llamada al sistema para creación de procesos.
·
Una petición de
usuario para crear un proceso.
·
El inicio de un
trabajo por lotes.
2.1.2. Terminación de un
proceso
Una
vez que se crea un proceso, empieza a ejecutarse y realiza el trabajo al que
está destinado. Sin embargo, cada proceso tiene su fin. Tarde o temprano el
nuevo proceso terminara, por lo general debido a una de las siguientes
condiciones:
·
Salida
normal (voluntaria).
·
Salida por
error (voluntaria).
·
Error fatal
(involuntaria).
·
Eliminado
por otro proceso (involuntaria).
La
mayoría de los procesos terminan debido a que ha concluido su trabajo.
2.1.3. Estado de un
proceso
En
la figura 2.1 podemos ver un diagrama de estados que muestra los tres estados
en los que se puede encontrar un proceso:
Figura 2.1. Un proceso puede estar en estado de ejecución, bloqueado o listo, con
diferentes transiciones entre estos estados. Fuente: Tanenbaum, Andrew s. y.
(2009). sistemas operativos modernos.
Hay
4 transiciones posibles como se muestra en la imagen. La transición 1 ocurre
cuando el sistema operativo descubre que un proceso no puede continuar justo en
ese momento. La transición 2 y 3 dependen del planificador de procesos, la
transición 2 ocurre cuando el planificador decide que el proceso en ejecución
se ha ejecutado el tiempo suficiente para que a si otro proceso tenga el tiempo
de los recursos de la CPU. La transición 3 ocurre cuando todos los procesos han
tenido su tiempo de la CPU y es momento de que el primer proceso obtenga la CPU
para ejecutarse nuevamente. La transición 4 ocurre cuando se Produce un evento
externo.
2.2. Hilos (Threads)
En
los sistemas operativos tradicionales, cada proceso tiene un espacio de
direcciones y un solo hilo de control. Los hilos de ejecución o threads,
surgieron con la necesidad de que existieran aplicaciones que realizaran varias
acciones a la vez, así como una mayor libertad en cuanto a seguir una sola
línea de ejecución y realizar varias tareas simultaneas. Es más preferible que
un programa siga funcionando, ejecutando otras acciones simultaneas. Se puede
decir que un hilo es una mínima parte de un proceso o miniprocesos.
Una
de las principales razones por la que es necesario utilizar hilos es que en
muchas aplicaciones se desarrollan varias actividades a la vez. Una segunda razón
para tener hilos es porque son más ligeros que los procesos, son más rápidos de
crear y destruir, la creación de hilos es de 10 a 100 veces más rápidas que la
de un proceso. Otra razón de tener hilos es el rendimiento. Los hilos no
producen un aumento en el rendimiento cuando todos ellos están ligados a la
CPU, pero cuando hay una gran cantidad de demanda de E/S, al tener hilos estas
actividades se pueden traslapar, con lo cual se agiliza la velocidad de la
aplicación.
En
la figura 2.2. (a) se observa tres
procesos con un hilo de control, por el lado contrario, en la figura (b) se observa un proceso que
ejecuta tres hilos de control.
Figura 2.2. (a) tres procesos son ejecutados cada uno con un hilo de control. (b)
un proceso es ejecutado con tres hilos de control. Fuente: Tanenbaum, Andrew s.
y. (2009). sistemas operativos modernos.
Cuando
un proceso con multihilamiento en sistema con una CPU, los hilos toman turnos
para ejecutarse. Los CPU conmutan rápidamente entre un hilo y otro, dando a
entender que los hilos se ejecutan en paralelo. Todos los hilos tienen el mismo
espacio de direcciones, lo cual significa que también comparten las mismas
variables globales. Como cada hilo puede acceder a cada dirección de memoria dentro
del espacio de direcciones del proceso, un hilo puede leer, escribir o incluso
borrar la pila de otro hilo.
En
la figura 2.3 se muestra una comparación que hay entre elementos por procesos y
elementos por hilo. Los elementos de la primera columna son propiedades de un proceso, no de un hilo.
Figura
2.3.
La primera columna muestra un listado de algunos elementos compartidos por
todos los hilos en un proceso. La segunda, algunos elementos que son privados
para cada hilo. Fuente: Tanenbaum, Andrew s. y. (2009).
sistemas operativos modernos.
2.3. Programación con
threads en linux
Actualmente
existen librerías o paquetes de threads que permiten escribir con varias líneas
de ejecución, sincronizadas a través de memoria compartida. Sin embargo, la
programación de hilos supone nuevas dificultades a comparación de la
programación secuencial.
Un
thread es su concepto más básico, puede ser visto como un simple flujo de
control secuencial. Con un único punto de ejecución, el programador no necesita
aprender nada nuevo para programar un único thread. Sin embargo, cuando se
tienen múltiples hilos, significa que el programa tiene distintos puntos de
ejecución. Las facilidades que nos ofrecen las librerías de hilos, son
conocidas como primitivas ligeras, y existen varios tipos:
·
Creación.
·
Mantenimiento.
·
Sincronización.
·
Destrucción.
2.3.1. Posix
Por
sus siglas en ingles Portable Operating System Interface (for Unix). Es un
estándar orientado a facilitar la creación de aplicaciones confiables y
portables. La mayoría de las versiones populares de UNIX cumplen este estándar
en gran medida. La biblioteca para el manejo de hilos en POSIX es Pthread.
2.3.2.
Gestión de Hilos
Un
paquete de manejo de hilos generalmente incluye funciones para: crear y
destruir un hilo, itineración, forzar exclusiones mutuas y espera condicionada.
Los hilos de un proceso comparten variables globales, descriptores, de archivos
abiertos, y puede cooperar o interferir con otros hilos. Todas las funciones de
hilos POSIX comienzan con pthread. Entre ellas están:
|
Función POSIX
|
Descripción
|
|
pthread_equal
|
Verifica
igualdad de dos identificadores.
|
|
pthread_self
|
Retorna ID del propio hilo.
|
|
pthread_create
|
Crea
un hilo.
|
|
pthread_exit
|
Termina el hilo sin terminar el proceso.
|
|
pthread_join
|
Espera
por el término de un hilo.
|
|
pthread_cancel
|
Termina otro hilo.
|
|
pthread_detach
|
Configura
liberación de recursos cuando termina.
|
|
pthread_kill
|
Envía una señal a un hilo.
|
Para
hacer uso de estas funciones, es necesario incluir la libreria
<pthread.h>.
Creación de un hilo (pthread_create)
Esta
función automáticamente pone en ejecución el hilo que crea. La sintaxis es la
siguiente:
int pthread_create( pthread_t *thread, const pthread_attr_t *attr, void
*(*start_routine)(void*), void *arg);
Donde:
- · El parámetro thread apunta al ID del hilo recientemente creado.
- · El parámetro attr representa un objeto atributo que encapsula los atributos de un hilo. Si attr es NULL, el hilo nuevo tendrá los atributos asignados por defecto.
- · El tercer parámetro, start_routine, es el nombre de la función que es invocada por el hilo cuando comienza su ejecución.
- · El parámetro arg especifica el parámetro que recibe la función start_routine.
Si
pthread_create se ejecuta satisfactoriamente retorna 0. Si la rutina no se
ejecuta satisfactoriamente retorna un código de error diferente de cero.
Salir (pthread_exit).
Un
hilo puede terminar de tres maneras sin terminar el proceso: retornando de su
rutina de inicio, cancelado por otro hilo del mismo proceso, o llamando
pthread_exit.
void
pthread_exit(void *value_ptr);
El
valor de value_ptr debe apuntar a datos que existan.
Unir (pthread_join)
La
función pthread_join suspende la ejecución del hilo que la invoca hasta que el
hilo objetivo, especificado por el primer parámetro, termine.
int pthread_join(pthread_t, void
**value_ptr);
El
parámetro valor_ptr es un apuntador al valor de retorno que el hilo objetivo
pasa a la función pthread_exit o a return. Si value_ptr es NULL, el hilo que
invoca join no recibe el valor de retorno del hilo objetivo.
Si
la función termina exitosamente retorna 0. Si no termina exitosamente retorna
un valor de error diferente de cero.
Referencias a hilos por su id (pthread_self)
Cualquier
hilo puede conocer su ID invocando a:
pthread_t
pthread_self(void);
Esta
función retorna el ID del hilo que la invoca.
Separar (pthread_detach)
Cuando
un hilo termina no libera sus recursos al menos que el hilo este separado. La función pthread_detach modifica las
opciones internas del hilo para especificar que el espacio utilizado para el
almacenamiento del mismo puede ser reclamado cuando culmine. Los hilos ya
separados no reportan su estado cuando culminan.
int pthread_detach( pthread_t thread );
La
función pthread_detach recibe un solo parámetro, thread, que es el ID del hilo
a ser separado. Si pthread_detach culmina exitosamente retorna uno. Si no
culmina exitosamente retorna un valor diferente de cero. La siguiente tabla
muestra los códigos de error de pthread_detach.
Cancelar
(pthread_cancel)
Un hilo invoca pthread_cancel para solicitar que
otro hilo sea cancelado. El resultado de la invocación es determinado por el
tipo del hilo objetivo y su estado de cancelación.
int pthread_cancel(pthread_t thread);
El parámetro de pthread_cancel es el ID del hilo
objetivo a ser cancelado. Si pthread_cancel se ejecuta exitosamente retorna 0.
Sino retorna un valor diferente de cero.
2.3.3. Mutex en Pthreads
Pthreads
proporciona varias funciones que se pueden utilizar para sincronizar los hilos.
El mecanismo básico utiliza una variable mutex, cerrada o abierta, para
resguardar cada región crítica. Un hilo que desea entrar a una región crítica
primero trata de cerrar el mutex asociado. Si el mutex está abierto, el hilo
puede entrar de inmediato y el bloqueo se establece en forma automática,
evitando que otros hilos entren; si el mutex ya se encuentra cerrado, el hilo
que hizo la llamada se bloquea hasta que el mutex esté abierto. Si hay varios
hilos esperando el mismo mutex, cuando está abierto sólo uno de ellos podrá
continuar y volver a cerrarlo. Estos bloqueos no son obligatorios. Es
responsabilidad del programador asegurar que los hilos los utilicen de manera
correcta.
3.
Desarrollo
3.1.
Requerimientos
Para
el desarrollo de la práctica, se realiza en un ambiente Linux, en este caso se
realiza la practica con la distribución OpenSuse 12.2. Teniendo el sistema
operativo Linux, se descarga el compilador gcc. Por ello será necesario
ejecutar en la terminal el siguiente comando:
Sudo
zypper install gcc.
Una
vez instalado se procede a corroborar que efectivamente el compilador gcc se ha
instalado. Es por ello que es necesario ejecutar en la terminal el siguiente
comando:
gcc
–v
Este
comando muestra la versión instalada en el sistema operativo. En la figura 3.1.
muestra la ejecución del comando.
Figura 3.1. Se observa la versión del compilador gcc en
este caso se cuenta con la versión 4.7.1.
3.2.
Ejecución del programa.
Se
cuenta con un programa desarrollado en el lenguaje de programación C, en donde
se muestra el funcionamiento de threads. La ejecución de este programa se
comporta de la siguiente manera: Se crean varios hilos a la vez y al mismo
tiempo comienza con su ejecución.
En
la figura 3.2 se muestra el código en general, más adelante se explica dicho
código.
Para
ejecutar el programa, es necesario abrir la terminal y ubicarse en la carpeta
en donde se encuentra el programa, en este caso se accedió a la carpeta de
documentos con el siguiente comando:
cd
Documentos/
Hecho
lo anterior, se procede a compilar el programa con el siguiente comando:
gcc
main.c –o hilo –l pthread
En la figura 3.3 se observa la
terminal con el comando tecleado para compilar el programa. Cabe señalar que se invoca la librería phtread.

Como
ya no existe errores de sintaxis, se crea el archivo ejecutable hilo, solo
queda ejecutarlo con el siguiente comando:
./hilo
En la figura 3.4. se muestra la ejecución del
programa.
Figura 3.4. Se observa el programa ejecutado,
se crean varios hilos a la vez y al mismo tiempo comienza con su
ejecución.
3.3. Analizando el código
En
primer lugar, se invocan las librerías necesarias a utilizar, también se define
una variable estática en donde contendrá el número de hilos a crear durante el
programa.
#include
<pthread.h>
#include
<stdio.h>
#define
NUM_THREADS 5
A
continuación, se implementa la función que realizan los hilos definidos, esta
función realiza la impresión del hilo que se encuentra ejecutando en este
momento. Recibe como parámetro el número de hilo, pero como apuntador, por lo
que se hace un cast para obtener su valor original.
//
La función que es utilizada en un Hilo
//
deberá de retornar un puntero sin tipo
void
*imprimeSaludo(void *threadid){
long tid;
// Aquí se castea lo que le fue pasado
// al hilo como atributos
tid = (long)threadid;
printf("Se esta ejecutando el hilo %ld
\n", tid);
pthread_exit(NULL); } //Se finaliza la ejecución del Hilo
Por
último, dentro del main, se crea la variable que creara los hilos, tomando como
longitud el número de la variable especificada anteriormente, creando cada hilo
en un for, llamando a su ejecución al mismo tiempo.
int
main ()
{
pthread_t threads[NUM_THREADS]; //Direccion donde se almacenarán los hilos (5
en total)
int rc;
int t;
for(t=0; t < NUM_THREADS; t++){
printf("Creando Hilo %ld \n",
t);
rc = pthread_create(&threads[t],
NULL, imprimeSaludo, (void *)t);
if (rc){
printf("ERROR; return code from
pthread_create() is %d \n", rc);
exit(-1);
}
//pthread_join(threads[t],NULL); //Se espera a que finalice el hilo
}
pthread_exit(NULL); //Se finaliza la ejecución del Hilo
}
En
la función rc = pthread_create(&threads[t], NULL, imprimeSaludo, (void *);
se utiliza una variable de tipo entero, la cual obtendrá un valor que
determinara si el hilo fue creado correctamente, en caso de que devuelva un 1
el hilo no se creó correctamente y pasara a informar el error.
Los
parámetros que recibe son:
·
El
parámetro &trheads[t] apunta al ID del hilo recientemente creado.
·
El
parámetro attr representa un objeto atributo que encapsula los atributos de un
hilo. En este caso es NULL, el hilo nuevo tendrá los atributos asignados por
defecto.
·
El tercer
parámetro, imprimeSaludo, es el nombre de la función que es invocada por el
hilo cuando comienza su ejecución.
·
El
parámetro void* especifica el parámetro que recibe la función start_routine.
Como
parte final, se corrobora que en la librería <pthread.h>, se encuentra
todas las funciones que existen y se utilizaron en el programa, así como su
calendarización que utilizan los hilos. La localización de la librería se
encuentra en:
En la figura 3.5 se observa la librería
pthread.h que fue ocupada en el código.
Figura 3.5. Se observa la librería ocupada
4.
Artículo
Implementación
de hilos-POSIX en paralelización y evaluación del rendimiento paralelo en el
nivel macro bloque de un decodificadorH.264 en una arquitectura multiprocesador
¬NUMA.
En este artículo de Redalyc se
toma principal mente la referencia en la utilización e implementación de la
programación en hilos, como trabajo primordial de este artículo es que se ha
investigado la paralelización y evaluación del rendimiento paralelo en el nivel
macro bloque de un decodificadorH.264 en una arquitectura multiprocesador ¬NUMA
haciendo uso de ello.
Se menciona que se ha
implementado una paralelización en una arquitectura multiprocesador. Tal que la
mejor estrategia de programación es la combinación de programación dinámica con
tailsubmit. Y hace referencia que la programación dinámica se ocupa del
problema de tiempo de decodificación variable al asignar MBs a los procesadores
de igual forma ya que han resuelto todas las dependencias. Pero debido al uso
dinámico, la programación sufre una gran sobrecarga de contención en una cola
de tareas centralizada e ineficiencias en el hilo API de sincronización.
Mediante el uso de la transmisión, el número de subprocesos que el acceso a la
cola de tareas se reduce la mayoría de los MB son procesado directamente por
los hilos de trabajo sin requerir ningún Sincronización adicional.
Además, de que explota localidad
de datos reduciendo la memoria externa en presión. Y de que hay una sobrecarga
considerable de hilo sincronización que proviene del muy bajo rendimiento de
los POSIX, semáforos en tiempo real en la arquitectura evaluada. El uso de
mecanismos de sincronización alternativos como el bloqueo algoritmos libres u
otra sincronización compatible con hardware mecanismos pueden ayudar a aumentar
la eficiencia de la paralelización
Se menciona que la posible
solución, es que la arquitectura, incluye soporte de hardware diseñado
específicamente para hilo en sincronización y programación. Al hacer eso, es
posible eliminar la sobrecarga de sincronización y aumento de subprocesos, y
eficiencia de la paralelización.
Y que otra limitación importante
de la escalabilidad de paralelización, es el rendimiento de la memoria en una
arquitectura NUMA sin accesos predecibles, como los realizados en el movimiento
etapa de compensación, resulta en accesos de descarga que tienen una alta
latencia. En una arquitectura multinúcleo esto puedo definirse como una gran
demanda de ancho de banda para la memoria externa. Esto es un problema abierto
y se menciona que están trabajando en optimizaciones de hardware estrategias
para aumentar la localidad de los accesos de datos.
5.
Conclusión
En dicha práctica se hace mención
acerca de los hilos, ya que los hilos son generados a partir de la creación de
un proceso, podemos decir que un proceso es un hilo de ejecución.
Hablar de hilos tienen muchas
ventajas ya que también pueden ser mejor conocidos como Multihilos, que es
cuando un proceso tiene múltiples hilos de ejecución los cuales realizan
actividades distintas, que pueden o no ser cooperativas entre sí. Los
beneficios de los hilos se derivan de las implicaciones de rendimiento.
Al igual que los procesos, los
hilos poseen un estado de ejecución y pueden sincronizarse entre ellos para
evitar problemas de compartición de recursos. Generalmente, cada hilo tiene una
tarea específica y determinada, como forma de aumentar la eficiencia del uso
del procesador.
6.
Bibliografía
Tanenbaum, Andrew s. y. (2009).
sistemas operativos modernos. México: Pearson educación.
Abraham Silberschatz.
(2011). Operating System Concepts. 2012, de Operating System Sitio web: http://iips.icci.edu.iq/images/exam/Abraham-Silberschatz-Operating-System-Concepts---9th2012.12.pdf
Claudia. (lunes, 19 de
septiembre de 2011). sistemas operativos. Recuperado el 10 de febrero de 2018,
de sistemas operativos: http://claus-sistemasoperetivos.blogspot.mx/2011/09/identificador-de-procesos.html
Alvarez, Mauricio; Ramirez,
Alex; Valero, Mateo; Azevedo, Arnaldo; Meenderinck, Cor; Juurlink, Ben. (1, junio,
2009). Evaluación del rendimiento paralelo en el nivel macro bloque del
decodificador H.264 en una arquitectura multiprocesador ccNUMA. Revista Avances
en Sistemas e Informática, vol. 6, pp. 219-228.






