En aplicaciones monohilo, es común usar variables globales para compartir datos entre funciones. En entornos multihilo, el espacio de datos es compartido, por lo que las variables globales también lo son. Sin embargo, a veces es necesario tener variables "globales" que sean privadas para cada hilo, accesibles a través de múltiples funciones dentro de ese hilo específico. Un ejemplo es cuando cada hilo necesita mantener su propia estructura de datos, como una lista enlazada, y utilizar funciones idénticas para manipularlas. La biblioteca de hilos POSIX proporciona un mecanismo para esto: Datos Específicos del Hilo (Thread-specific Data, TSD).
Creación y Eliminación de Claves TSD
POSIX define dos funciones principales para gestionar TSD:
pthread_key_create
Esta función reserva una entrada en el conjunto de TSD y asigna un identificador (pthread_key_t) para su uso posterior. Opcionalmente, se puede proporcionar una función de destructor (destr_function). Si se especifica, esta función se llamará cuando el hilo finalice (usando pthread_exit), pasando como argumento el dato asociado a esa clave TSD para su liberación.
La clave creada es accesible para todos los hilos, pero cada hilo puede asociar un valor diferente a esa clave. En la implementación de LinuxThreads, las TSD se gessionan mediante un array de estructuras:
static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] = { { 0, NULL } };
La creación de una TSD implica marcar una entrada en este array como "en uso" y devolver su índice. El destructor también se almacena en esta estructura.
pthread_key_delete
Para eliminar una TSD, se utiliza esta función:
int pthread_key_delete(pthread_key_t key);
Esta función no verifica si algún hilo está utilizando actualmente la TSD ni llama a la función destrutcora. Simplemente libera la entrada para que pueda ser reutilizada por futuras llamadas a pthread_key_create.
Acceso a Datos TSD
La lectura y escritura de datos TSD se realizan mediante funciones específicas:
pthread_setspecific
Asocia un puntero (void *pointer) a una clave TSD dada. Es importante notar que se almacena el valor del puntero, no el contenido al que apunta.
int pthread_setspecific(pthread_key_t key, const void *pointer);
pthread_getspecific
Recupera el valor del puntero asociado a una clave TSD específica.
void * pthread_getspecific(pthread_key_t key);
Ambas funciones utiliazn void *, permitiendo almacenar cualquier tipo de dato. En LinuxThreads, estos datos se almacenan en un array bidimensional de punteros dentro de la estructura de descripción del hilo (_pthread_descr_struct). La ubicación específica se calcula a partir del valor de la clave:
#define PTHREAD_KEY_2NDLEVEL_SIZE 32
#define PTHREAD_KEY_1STLEVEL_SIZE ((PTHREAD_KEYS_MAX + PTHREAD_KEY_2NDLEVEL_SIZE - 1) / PTHREAD_KEY_2NDLEVEL_SIZE)
// Donde PTHREAD_KEYS_MAX suele ser 1024.
// idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZE
// idx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE
Esto efectivamente crea una matriz dispersa de 32x32 para almacenar los datos TSD. El acceso se realiza calculando los índices correspondientes a partir de la clave.
Ejemplo de Uso
El siguiente código ilustra cómo utilizar TSD para almacenar datos privados por hilo. Se crean dos hilos, cada uno asignando su propio ID de hilo a la misma clave TSD. La ejecución separada de las operaciones de escritura y lectura demuestra la privacidad de los datos. Al finalizar los hilos, la función destructora se ejecuta automáticamente para limpiar los datos asociados.
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// Función destructora para liberar la memoria TSD
void destructor_tsd(void *valor) {
printf("Hilo %lu: Liberando TSD con valor %p\n", (unsigned long)pthread_self(), valor);
// Aquí iría la lógica de liberación de memoria si fuera necesario
// free(valor);
}
// Función ejecutada por los hilos
void *funcion_hilo(void *arg) {
pthread_t tid = pthread_self();
pthread_key_t clave_mi_tsd;
// Crear la clave TSD (esto se hace por hilo en este ejemplo para simplificar)
// En un escenario real, la clave se crearía una vez y se compartiría.
if (pthread_key_create(&clave_mi_tsd, destructor_tsd) != 0) {
perror("Error al crear la clave TSD");
return NULL;
}
// Asignar el ID del hilo como dato específico del hilo
// Convertimos el tid a void* para almacenarlo
if (pthread_setspecific(clave_mi_tsd, (void *)tid) != 0) {
perror("Error al establecer TSD");
return NULL;
}
printf("Hilo %lu: Establecido TSD a %lu\n", (unsigned long)tid, (unsigned long)tid);
// Pequeña pausa para permitir que el otro hilo se ejecute
sleep(1);
// Recuperar y mostrar el dato específico del hilo
void *dato_recuperado = pthread_getspecific(clave_mi_tsd);
if (dato_recuperado != NULL) {
printf("Hilo %lu: Recuperado TSD = %lu\n", (unsigned long)tid, (unsigned long)dato_recuperado);
} else {
printf("Hilo %lu: No se encontró TSD.\n", (unsigned long)tid);
}
// No es necesario llamar a pthread_key_delete aquí si la clave se crea por hilo.
// Si la clave fuera global, se necesitaría eliminarla al final.
// pthread_key_delete(clave_mi_tsd); // Si la clave fuera global y se quisiera eliminar
return NULL;
}
int main() {
pthread_t hilo1, hilo2;
printf("Creando hilo 1...\n");
if (pthread_create(&hilo1, NULL, funcion_hilo, NULL) != 0) {
perror("Error al crear hilo 1");
return 1;
}
printf("Creando hilo 2...\n");
if (pthread_create(&hilo2, NULL, funcion_hilo, NULL) != 0) {
perror("Error al crear hilo 2");
return 1;
}
// Esperar a que ambos hilos terminen
pthread_join(hilo1, NULL);
pthread_join(hilo2, NULL);
printf("Ambos hilos han terminado.\n");
return 0;
}