Programación Concurrente: Fundamentos de Hilos en Sistemas Operativos

Conceptos Avanzados de Concurrencia

Tema 4: Programación con Hilos

I. Fundamentos Teóricos

A. Naturaleza de los Hilos

1. Funcionamiento Interno

2. Ventajas del Modelo de Hilos

  • a) Eficiencia en Creación y Conmutación

Los hilos requieren menos recursos del sistema que los procesos tradicionales. La creación de un nuevo hilo consume considerablemente menos tiempo que generar un proceso independiente, debido a que no se necesita copiar todo el espacio de direcciones del proceso padre.

  • b) Capacidad de Respuesta

Los sistemas basados en hilos mejoran la capacidad de respuesta de las aplicaciones. Cuando un hilo está bloqueado esperando operaciones de entrada/salida, otros hilos del mismo proceso pueden continuar ejecutándose, permitiendo que la aplicación permanezca receptiva.

  • c) Adecuación para Cálculos Paralelos

Los hilos permiten distribuir cargas de trabajo computacional entre múltiples unidades de procesamiento. Esta característica resulta especialmente valiosa en sistemas multinúcleo, donde cada hilo puede ejecutarse simultáneamente en un procesador diferente.

3. Desventajas del Modelo

  • a) Necesidad de Sincronización Explícita

Al compartir el mismo espacio de direcciones, los hilos acceden a los mismos datos y recursos compartidos. Sin mecanismos de coordinación adecuados, pueden surgir inconsistencias en los datos.

  • b) Compatibilidad con Bibliotecas Externas

Algunas bibliotecas heredadas no fueron diseñadas considerando el entorno multihilo. Estas bibliotecas pueden presentar comportamientos inesperados cuando se utilizan desde múltiples hilos simultáneos.

  • c) Rendimiento en Sistemas Monoprocesador

En sistemas con un único procesador, los hilos no proporcionan ventajas de rendimiento reales. El cambio de contexto entre hilos añade overhead adicional que puede hacer que la ejecución sea más lenta comparada con un programa secuencial tradicional.

B. Operaciones con Hilos

C. Interfaz de Programación Pthread

La biblioteca Pthread ofrece un conjunto completo de funciones para la manipulación de hilos en sistemas Unix/Linux.

pthread_create(identificador, atributos, rutina, parametros): iniciar nuevo hilo
pthread_exit(estado)                    : finalizar hilo actual
pthread_cancel(identificador)            : solicitar cancelación
pthread_attr_init(config)                : configurar atributos
pthread_attr_destroy(config)             : liberar recursos de atributos


1. Inicialización de un Hilo

La función pthread_create() permite crear un nuevo hilo de ejecución dentro de un proceso.

int pthread_create(pthread_t *id_hilo, pthread_attr_t *configuracion, 
                   void *(*rutina)(void *), void *argumento);


El valor de retorno indica éxito (cero) o error (código negativo). Los parámetros de esta función incluyen:

  • id_hilo: Puntero a una variable de tipo pthread_t que almacenará el identificador único asignado por el kernel del sistema operativo al nuevo hilo.

2. Identificador de Hilo

3. Terminación de Hilos

4. Unión de Hilos

D. Ejemplo Práctico (ver sección de ejercicios)

1. Cálculo de Matrices mediante Hilos

2. Ordenamiento Rápido Paralelizado

E. Mecanismos de Sincronización

  • Condiciones de Carrera: Situación que ocurre cuando múltiples hilos attemptan modificar simultáneamente una variable o estructura de datos compartida, siando el resultado final dependiente del orden específico de ejecución de los hilos.

1. Mutex (Exclusión Mutua)

El mecanismo más básico de sincronización consiste en un bloqueo que permite el acceso exclusivo a una sección crítica. En el contexto de Pthread, estos bloqueos se denominan mutex, abreviatura de exclusión mutua. Las variables mutex se declaran utilizando el tipo pthread_mutex_t y requieren inicialización previa a su uso. Existen dos enfoques para inicializar un mutex:

  1. Inicialización estática: Se define una variable mutex con atributos por defecto utilizando la macro pthread_mutex_t bloqueo = PTHREAD_MUTEX_INITIALIZER.
  2. Inicialización dinámica: Mediante la función pthread_mutex_init(), que permite configurar atributos específicos a través del parámetro de configuración, como: pthread_mutex_init(pthread_mutex_t *bloqueo, pthread_mutexattr_t *config); Generalmente, este parámetro puede ser NULL para utilizar los atributos predeterminados.

2. Prevención de Interbloqueos

Los mutex employan protocolos de bloqueo. Cuando un hilo no puede adquirir un mutex, queda en estado de espera hasta que el mutex sea liberado. El uso inadecuado de bloqueos genera problemas críticos, siendo el interbloqueo (deadlock) el más prominente. El interbloqueo es una condición donde múltiples entidades de ejecución quedan bloqueadas esperando recursos que nunca se liberarán, creando un ciclo de dependencias que impide cualquier avance.

Al igual que las condiciones de carrera, los interbloqueos deben eliminarse completamente de programas concurrentes. Existen diversas estrategias para manejar estos problemas: prevención, evasión, detección y recuperación. En sistemas reales, la prevención de interbloqueos constituye el enfoque más práctico, integrando consideraciones de seguridad desde el diseño del algoritmo paralelo.

Una técnica simple de prevención consiste en establecer un orden consistente para la adquisición de mutex, asegurando que cada hilo solicite los bloqueos en una única dirección, eliminando así la posibilidad de ciclos en las solicitudes.

Sin embargo, diseñar cada algoritmo paralelo exclusivamente con solicitudes unidireccionales resulta impracticable en muchas situaciones. En estos casos, la función pthread_mutex_trylock() ofrece una alternativa para prevenir interbloqueos. Si el mutex ya está bloqueado, trylock() retorna inmediatamente con un error. El hilo invocador puede entonces liberar algunos de los mutex que ya posee y reintentar la operación, permitiendo que otros hilos continúen su ejecución.

/*Hilo Principal*/
while(1){
    lock(bloqueo1);
    if(!trylock(bloqueo2))
        unlock(bloqueo1);
    else
        break;
}


3. Variables de Condición

4. Problema del Productor-Consumidor

5. Semáforos

Los semáforos constituyen un mecanismo general de sincronización entre procesos e hilos.

6. Barreras de Sincronización

7. Solución Concurrentede Ecuaciones Lineales

8. Implementación de Hilos en Linux

A diferencia de muchos sistemas operativos modernos, Linux no establece una distinción fundamental entre procesos y hilos. Para el kernel de Linux, un hilo es simplemente un proceso que comparte ciertos recursos con otros procesos. Tanto procesos como hilos se crean mediante la llamada al sistema clone(), cuya firma es:

int clone(int (*funcion)(void *), void *pila_hijo, int banderas, void *argumento)


Esta llamada al sistema se asemeja más a una función de creación de hilos. Crea un proceso hijo que ejecuta la función proporcionada con la pila especificada. El parámetro de banderas indica específicamente qué recursos se comparten entre el proceso padre y el hilo hijo.

Publicado el 6-19 20:55