Otro ejemplo de paquete de hilos de nivel usuario es el de la Distributed Computing Environment (Entorno de Cómputo Distribuido, DCE) de la Open Software Foundation (Fundación de Software Abierto, OSF). Este paquete de nivel usuario es grande y complejo, como la mayoría del software de la OSF. Contiene un total de 51 primitivas (procedimientos de biblioteca) relacionadas con los hilos, y que pueden ser utilizadas por los programas de usuario. Algunas de ellas no son estrictamente necesarias, pero se presentan para ahorrar tiempo y esfuerzo de programación al usuario.
DCE Threads es un paquete que supera el límite, en términos de soporte de hilos, de muchos sistemas operativos, creando un entorno multihilo. Incluso los sistemas operativos que tienen servicios para gestión de hilos pueden beneficiarse de DCE Threads, ya que los hilos DCE estandarizan los servicios de hilos, haciendo las aplicaciones basadas en hilos más fácilmente portables. La portabilidad dependerá también de otros factores. Además algunos servicios de los hilos DCE no son portables. Son aquellos que terminan con el sufijo _np.
El paquete de hilos DCE está basado en un borrador del estándar POSIX 1003.1c sobre interfaces de hilos. Este paquete es importante para el entorno DCE, ya que algunos otros componentes tecnológicos suponen la disponibilidad de soporte de hilos. El paquete ha sido implementado para diversos sistemas operativos, tanto aquellos que implementan hilos a nivel kernel (OSF/1, AIX y OS/2), como aquellos que no implementan hilos a nivel kernel (Windows 3.x) con lo cual el núcleo no tiene conocimiento de la existencia de los hilos de usuario.
Los componentes de los hilos DCE proporcionan los siguientes servicios :
El código hilo-reentrante es el código que puede ser ejecutado por múltiples hilos al mismo tiempo. Este tipo de código es hilos-seguro (puede ser ejecutado de forma segura por varios hilos), pero código hilo-seguro puede no ser hilo-reentrante, en el sentido de que puede ser ejecutado por múltiples hilos, pero no al mismo tiempo, ya que cuando uno de los hilos ejecuta el código, los otros estarán bloqueados hasta que el primero finalice, para después entrar en ejecución uno de los que esperaba. También existe un mecanismo de bloqueo global disponible para hacer el código no reentrante seguro.
La compatibilidad con sistemas operativos que trabajen con procesos de hilo simple se consigue mediante el empleo de rutinas jacket, para ser usadas junto con las librerías existentes y las llamadas modificadas al sistema. Es importante ser capaz de usar llamadas no reentrantes, y realizar llamadas seguras para no bloquear el proceso entero, sino sólo el hilo que realiza la llamada. Los jackets son colocados a modo de macros que transforman los nombres de las llamadas al sistema en llamadas hilo-seguras. Para emplear los jackets se debe incluir el fichero <pthread.h> en compilación.
En el caso de OS/2, los servicios de hilos DCE son transformados a servicios nativos del sistema operativo, ya que dispone de gestión de hilos nativa. En cambio, en los sistemas operativos donde el soporte de hilos no está disponible, como ocurren en Windows 3.x, los hilos DCE implementan los hilos en el nivel de usuario, sin que el kernel tenga conocimiento de su existencia.
Los hilos DCE proporcionan las siguientes formas de manejar los errores de una rutina de un hilo :
El programador debe elegir uno de los dos métodos
antes de escribir un programa de hilos, ya que estos métodos
no pueden ser usados conjuntamente en el mismo módulo de
código. Incluyendo el fichero <pthread.h> se elige
la opción de retorno de un valor de estado. Alternativamente,
incluyendo <pthread_exc.h> emplearemos el manejo de excepciones.
Un área importante de interés cuando se programa con hilos es la sincronización de los mismos. Las facilidades de sincronización de hilos son esenciales para implementar los distintos modelos de programación.
DCE Threads ofrece las siguientes facilidades de sincronización :
Los hilos se pueden ejecutar bajo varias políticas de planificación. DCE Threads ofrece las siguientes :
Las llamadas al paquete que proporcionan la funcionalidad del paquete, se agrupan en 7 categorías, cada una de las cuales se refiere a un aspecto distinto de los hilos y su uso :
1) Creación y gestión de hilos :
- pthread_create: Crea un nuevo hilo
- pthread_exit: Llamada por un hilo al terminar su labor.
- pthread_join: Equivalente a la llamada de sistema WAIT en UNIX.
- pthread_detach: Renuncia del hilo padre a la espera de la salida del hilo hijo.
- pthread_yield : El hilo indica la liberación de la CPU voluntariamente.
Son un conjunto de llamadas que permiten la creación de hilos y su finalización al terminar el trabajo. Un hilo padre puede esperar al hilo hijo mediante join, de manera similar a la llamada de sistema WAIT de UNIX. Si el padre no necesita esperar al hijo, puede renunciar a él mediante detach.
El paquete DCE permite la creación, destrucción y gestión de patrones (templates) o atributos para hilos, mútex y variables de condición, por parte del usuario, lo cual resulta bastante útil a la hora de programar. Los patrones se pueden configurar con valores iniciales por defecto.
A continuación se describe con mayor detalle
alguna de las llamadas que proporciona DCE Threads para la creación
y gestión de hilos :
pthread_create()
Crea un objeto hilo y un hilo. El objeto hilo define y controla la ejecución del hilo.
El prototipo para la llamada pthread_create() está declarado en el fichero de cabecera <pthread.h>. A continuación se muestra la definición de la llamada con sus argumentos :
int pthread_create (pthread_t *thread, pthread_attr_t attr, pthread_startroutine_t start_routine , pthread_addr_t arg);
pthread_exit()
Finaliza el hilo que realiza la llamada y crea un código de estado disponible para cualquier hilo que llame a pthread_join() y especifique a este hilo.
El prototipo para la llamada pthread_exit() está declarado en el fichero de cabecera <pthread.h>. A continuación se muestra la definición de la llamada con sus argumentos :
void pthread_exit (pthread_addr_t status);
pthread_yield()
Notifica al planificador que el hilo que realiza la llamada liberará voluntariamente la ejecución para cualquier hilo en espera con la misma prioridad. Esta llamada puede ser muy útil para garantizar la recepción de sus propias señales a un hilo.
El prototipo para la llamada pthread_yield() está declarado en el fichero de cabecera <pthread.h>. A continuación se muestra la definición de la llamada :
void pthread_yield ();
La rutina disponible en DCE Threads para proporcionar la facilidad join es :
pthread_join()
Provoca que el hilo llamador espere a la terminación del hilo especificado.
El prototipo para la llamada pthread_join() está declarado en el fichero de cabecera <pthread.h>. A continuación se muestra la definición de la llamada con sus argumentos :
int pthread_join (pthread_t thread, pthread_addr_t *status);
2) Gestión de Patrones:
Cuando se crea un objeto (hilo, mútex o variable de condición), uno de los parámetros de la llamada create es un puntero al patrón o atributos. Los patrones evitan la necesidad de especificar todas las opciones mediante parámetros independientes. En las distintas versiones del paquete, las llamadas create han seguido siendo las mismas, lo que se ha hecho es ir añadiendo nuevos atributos (propiedades) a los patrones.
Llamadas para la gestión de patrones:
a) Patrones de hilos :
- pthread_attr_create: Crear patrón para establecer los parámetros del hilo.
- pthread_attr_delete: Eliminar patrón para los hilos.
- pthread_attr_setprio : Establecer la prioridad de planificación en el patrón.
- pthread_attr_getprio: Leer la prioridad preestablecida de planificación.
- pthread_attr_setsched : Establece la política de planificación.
- pthread_attr_setstacksize: Establecer el tamaño preestablecido de pila en el patrón.
- pthread_attr_getstacksize: Leer el tamaño preestablecido de pila en el patrón.
b) Patrones de mútex :
- pthread_attr_mutexattr_create: Crear patrón para los parámetros del mútex.
- pthread_attr_mutexattr_delete: Borrar patrón para los parámetros del mútex.
- pthread_attr_mutexattr_setkind_np: Establecer tipo preestablecido de mútex.
- pthread_attr_mutexattr_getkind_np : Leer el tipo preestablecido de mútex en el patrón.
c) Patrones de variables de condición:
- pthread_attr_condattr_create: Crear patrón para las variables de condición.
- pthread_attr_condattr_delete: Eliminar patrón
para las variables de condición.
Existen llamadas para crear y eliminar patrones para
los hilos, llamadas para leer y escribir atributos de los patrones,
como el tamaño de pila y los parámetros de planificación
(prioridad, etc.), empleados al crear hilos usando el patrón.
La política de planificación puede seleccionarse empleando la siguiente rutina :
pthread_attr_setsched()
Establece una política de planificación de un atributo de hilo para ser usado durante la creación de hilos.
El prototipo para la llamada pthread_attr_setsched() está declarado en el fichero de cabecera <pthread.h>. A continuación se muestra la definición de la llamada con sus argumentos :
int pthread_attr_setsched (pthread_attr_t attr, int scheduler);
Otras llamadas permiten crear y eliminar patrones
para mútex y variables de condición.
3) Gestión de Mútex:
Los mútex se pueden crear y destruir de manera dinámica. Los procedimientos de manejo de los mútex son:
- pthread_mutex_init: Crea un mútex.
- pthread_mutex_destroy: Elimina un mútex.
- pthread_mutex_lock: Cierra un mútex. Bloqueo si ya estaba cerrado.
- pthread_mutex_trylock: Intento de cerrar un mútex. Falla si ya estaba cerrado.
- pthread_mutex_unlock: Elimina la cerradura de un
mútex.
El problema de los mútex es que no está resuelto el caso en el que se elimina una cerradura y después se intenta el cierre. El problema de los "despertares perdidos" fue resuelto por Dijkstra mediante la invención de los semáforos. Sin embargo en los mútex DCE, el orden de estos pasos puede o no importar según la implementación del paquete, lo cual ocasiona problemas a la hora de escribir aplicaciones portables correctas.
Existen dos tipos de mútex, según funcionen las cerraduras anidadas:
Muchos diseñadores difieren, a la hora de
implementar los mútex, entre una y otra política
en los cierres. Algunas de las rutinas disponibles en DCE Threads
para inicializar y gestionar mútex son :
pthread_mutex_init()
Crea un mútex y le inicializa al estado desbloqueado.
El prototipo para la llamada pthread_cancel() está declarado en el fichero de cabecera <pthread.h>. A continuación se muestra la definición de la llamada con sus argumentos :
int pthread_mutex_init (pthread_mutex_t *mutex, pthread_mutexattr_t attr);
pthread_mutex_lock()
Bloquea un mútex desbloqueado. Si el mútex ya estaba bloqueado, el hilo llamador es puesto a esperar. Por defecto, si un hilo intenta volver a bloquear un mútex que bloqueó anteriormente, sin desbloquearle primero, el hilo entrará en una situación de deadlock.
El prototipo para la llamada pthread_mutex_lock () está declarado en el fichero de cabecera <pthread.h>. A continuación se muestra la definición de la llamada con sus argumentos :
int pthread_mutex_lock (pthread_mutex_t *mutex);
pthread_mutex_unlock()
Desbloquea un mútex. Si hay hilos esperando a que el mútex sea desbloqueado, uno de ellos continuará la ejecución, bloqueando el mútex. Si un hilo desbloquea un mútex que pertenece a otro hilo o desbloquea un mútex ya desbloqueado los resultados son impredecibles.
El prototipo para la llamada pthread_mutex_unlock () está declarado en el fichero de cabecera <pthread.h>. A continuación se muestra la definición de la llamada con sus argumentos :
int pthread_mutex_unlock (pthread_mutex_t *mutex);
4) Gestión de Variables de Condición:
Las variables de condición también se pueden crear y destruir dinámicamente. Un hilo se puede bloquear a la espera de una variable de condición para un cierto recurso.
Las primitivas que permiten la gestión de variables de condición son:
- pthread_cond_init: Crea una variable de condición.
- pthread_cond_destroy: Elimina una variable de condición.
- pthread_cond_wait: Espera de una variable de condición hasta que se produce una señal.
- pthread_cond_signal: Despierta a lo más un hilo que espera a una variable de condición.
- pthread_cond_broadcast: Despierta a todos los hilos que esperan a una variable de condición.
Existe una operación que despierta a un sólo hilo, la señalización; y existe otra operación que despierta a todos los hilos a la espera, la transmisión (broadcast).
Algunas de las rutinas disponibles en DCE Threads
para inicializar y gestionar variables de condición son :
pthread_cond_init()
Crea e inicializa una variable de condición.
El prototipo para la llamada pthread_cond_init () está declarado en el fichero de cabecera <pthread.h>. A continuación se muestra la definición de la llamada con sus argumentos :
int pthread_cond_init (pthread_cond_t *cond, pthread_condattr_t attr);
pthread_cond_wait()
Provoca que un hilo espere hasta que se verifique la variable de condición. La llamada a esta rutina causa que el hilo desbloquee automáticamente el mútex de la variable de condición, que es especificado como uno de los argumentos. Si este mútex no fue bloqueado antes de invocar la llamada, los resultados son impredecibles. Después de que un hilo retorne de esta llamada, el mútex es automáticamente bloqueado de nuevo y propiedad del hilo que realizó la llamada..
El prototipo para la llamada pthread_cond_wait () está declarado en el fichero de cabecera <pthread.h>. A continuación se muestra la definición de la llamada con sus argumentos :
int pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex);
pthread_cond_signal()
Despierta a uno de los hilos que esperan por una variable de condición.
El prototipo para la llamada pthread_cond_signal () está declarado en el fichero de cabecera <pthread.h>. A continuación se muestra la definición de la llamada con sus argumentos :
int pthread_cond_signal (pthread_cond_t *cond);
pthread_cond_broadcast()
Despierta a todos los hilos que esperan por una variable de condición.
El prototipo para la llamada pthread_cond_broadcast () está declarado en el fichero de cabecera <pthread.h>. A continuación se muestra la definición de la llamada con sus argumentos :
int pthread_cond_broadcast (pthread_cond_t *cond);
5) Gestión de Variables Globales:
Existen también una seria de primitivas que permiten gestionar las variables globales en cada hilo. Este tipo de variables globales, son variables que puede utilizar cualquier procedimiento del hilo, pero no fuera de él. Son variables locales al hilo, no visibles por el resto de hilos, pero globales a todo el ámbito del hilo.
Los procedimientos de gestión de estas variables son:
- pthread_keycreate: Crea una variable global para este hilo, y devuelve un puntero a dicha variable.
- pthread_setspecific: Asigna un valor a una variable global del hilo.
- pthread_getspecific: Lee el valor de una variable global del hilo.
Este tipo de variables globales por hilo no suele
ser soportado por los lenguajes de programación tradicionales,
por lo cual se maneja en tiempo de ejecución, y se emplean
procedimientos para su gestión en vez de una sintaxis específica
del lenguaje. Este tipo de variables globales no está bien
considerado, y sólo se recomienda su uso en casos de emergencia.
6) Gestión de Eliminación de Hilos:
Un hilo puede ser eliminado por otro, pero esto puede ocasionar problemas, sobre todo si el primero tenía cerrado un mútex que el segundo necesitará a continuación. Por eso los hilos pueden activar o desactivar los intentos de eliminación por parte de otros hilos.
Los procedimientos para ello, son los siguientes:
- pthread_cancel: Intenta eliminar otro hilo.
- pthread_setcancel: Activa o Desactiva la capacidad
de otros hilos para eliminar este hilo.
pthread_cancel()
Solicita la terminación del hilo llamador o de otro hilo tan rápidamente como sea posible. La terminación ocurre de forma similar a la llamada pthread_exit().
El prototipo para la llamada pthread_cancel() está declarado en el fichero de cabecera <pthread.h>. A continuación se muestra la definición de la llamada con sus argumentos :
int pthread_cancel (pthread_t thread);
7) Gestión de la Planificación:
Los hilos de un proceso se pueden planificar según distintos algoritmos: FIFO, Round-Robin, con prioridades, sin prioridad, etc.
Existen una serie de llamadas que permiten configurar el algoritmo de planificación y las prioridades:
- pthread_setscheduler: Establece el algoritmo de planificación.
- pthread_getscheduler: Lee el algoritmo de planificación.
- pthread_setprio: Establece la prioridad de planificación.
- pthread_getprio: Lee la prioridad de planificación.
8) Código no seguro :
Uno de los problemas típicos encontrados por los programadores cuando trabajan con hilos es la necesidad de emplear código antiguo que no fue diseñado para trabajar con hilos, y que puede dar problemas en un entorno multihilo. Para usar código no hilo-seguro en un entorno multihilo se debe emplear un bloqueo global que usa un mútex global. Este bloqueo es empleado en la parte de código que llama a las rutinas o en las rutinas.
En el paquete DCE Threads existe un mútex global que se puede emplear para hacer seguro código que no lo es frente a múltiples hilos. Existen dos rutinas para bloquear y desbloquear el mútex global :
pthread_lock_global_np()
Establece el bloqueo del mútex global.
El prototipo para la llamada pthread_lock_global_np () está declarado en el fichero de cabecera <pthread.h>. A continuación se muestra la definición de la llamada con sus argumentos :
void pthread_lock_global_np();
pthread_unlock_global_np()
Libera el bloqueo del mútex global.
El prototipo para la llamada pthread_unlock_global_np () está declarado en el fichero de cabecera <pthread.h>. A continuación se muestra la definición de la llamada con sus argumentos :
void pthread_unlock_global_np();
Veamos un ejemplo real de una aplicación multihilo usando DCE Threads para el sistema operativo OS/2. La aplicación ejemplo que explicamos realiza una búsqueda de un número de teléfono y una dirección para un nombre dado como entrada. Constará de un cliente y dos servidores. El cliente obtendrá la entrada y visualizará la salida. Los servidores proporcionarán el número de teléfono y los servicios de búsqueda de directorio, respectivamente. El cliente finalizará cuando se introduzca una cadena de entrada nula. Los hilos serán explícitamente empleados en el cliente para obtener concurrentemente información para cada servidor y para realizar concurrentemente búsquedas en cada servidor.
El interfaz que describe la relación entre el cliente y los servidores para la aplicación del ejemplo es el fichero THREADS.IDL :
[ uuid(0000FD2A-6C72-1C17-B922-10005A4F5444), version(1.0) ] interface look { const short MAX_NAME_LENGTH = 60; const short MAX_DIR_ENTRY_LENGTH = 60; const char *PHON_OBJ_UUID = "007E649A-6CD6-1C17-8B39-10005A4F3057"; const char *ADDR_OBJ_UUID = "001B0832-6CAF-1C17-8BFC-10005A4F5444"; typedef [ string ] char name_t[ MAX_NAME_LENGTH + 1 ]; typedef [ string ] char dir_entry_t[ MAX_DIR_ENTRY_LENGTH + 1 ]; void lookup_dir ( [ in ] handle_t bh, [ in ] name_t name, [ out ] dir_entry_t dir_entry ); }
Ambos servidores utilizarán el mismo interfaz con diferentes identificadores de objetos UUIDs. Los servidores serán distinguidos por su UUID. Las cadenas de representaciones de objetos son definidas como constantes en el fichero .IDL.
El código para el servidor de búsqueda en el directorio telefónico y para el servidor de búsqueda en el directorio de direcciones es muy similar. Ambos reciben una cadena que representa un nombre como una clave con la cual realizar una búsqueda, devolviendo una cadena que representa una entrada del directorio que puede ser un número de teléfono o una dirección. La diferencia principal es el tipo y la longitud de la entrada del directorio. Los servidores devuelven un cadena de longitud cero si no se encuentra una entrada conteniendo el nombre.
El código gestor para el servidor de teléfonos
es el siguiente :
/******************************************************************************/ /* Módulo : phon_m.c */ /* Propósito: Código gestor servidor. Implementa la búsqueda de nos. teléfono */ /* Procedimiento que será llamado por la búsqueda del cliente. */ /******************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include "look.h" #define BUFFER_SIZE MAX_NAME_LENGTH + MAX_DIR_ENTRY_LENGTH + 1 #define PHONE_FIL "PHONE.FIL" /******************************************************************************/ /* Procedimiento : lookup_phon */ /* Propósito : Permite a un cliente realizar una búsqueda en el directorio */ /* telefónico usando un nombre de persona. */ /* Devuelve el número de teléfono de la persona si encuentra el */ /* nombre. Si no, una cadena de longitud cero es devuelta */ /******************************************************************************/ void lookup_phon ( rpc_binding_handle_t bh, name_t name, dir_entry_t phone ) { FILE *file_p; char buffer[ BUFFER_SIZE ]; /* Abrir fichero directorio */ file_p = fopen ( PHONE_FIL, "r" ); if ( file_p == NULL ) { fprintf ( stderr, "No puedo abrir el fichero %s\n", PHONE_FIL ); return; } /* Buscar la entrada de directorio conteniendo el nombre de persona */ printf ( "Buscando número de teléfono de %s\n", name ); while ( 1 ) { /* Devuelve una cadena nula si la búsqueda falla. */ if ( fgets ( buffer, BUFFER_SIZE, file_p ) == NULL ) { *phone = '\0'; break; } /* Devuelve un número de teléfono si la búsqueda tiene éxito */ if ( strstr ( buffer, name ) != NULL ) { buffer[ strlen ( buffer ) - 1 ] = '\0'; strcpy ( phone, &buffer[ strlen ( name ) + 1 ] ); break; } } /* Limpia y finaliza. */ fclose ( file_p ); return; } /* Declara e inicializa la tabla EPV del gestor de búsqueda de teléfonos */ look_v1_0_epv_t epv_phon = { lookup_phon };
El código del gestor para el servidor de direcciones es el siguiente :
/******************************************************************************/ /* Módulo : addr_m.c */ /* Propósito : Código gestor del servidor. Implementa el procedimiento de */ /* búsqueda de dirección que será llamado por la búsqueda del cliente */ /******************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include "look.h" #define BUFFER_SIZE MAX_NAME_LENGTH + MAX_DIR_ENTRY_LENGTH + 1 #define ADDRESS_FIL "ADDRESS.FIL" /******************************************************************************/ /* Procedimiento: lookup_addr */ /* Propósito : Permite que un cliente haga una búsqueda en el directorio de */ /* direcciones usando un nombre de persona. Devuelve la dirección */ /* de la persona si el nombre es encontrado. Si no, devuelve una */ /* cadena de longitud cero. */ /******************************************************************************/ void lookup_addr ( rpc_binding_handle_t bh, name_t name, dir_entry_t address ) { FILE *file_p; char buffer[ BUFFER_SIZE ]; /* Abrir fichero directorio */ file_p = fopen ( ADDRESS_FIL, "r" ); if ( file_p == NULL ) { fprintf ( stderr, "No puedo abrir fichero %s\n", ADDRESS_FIL ); return; } /* Buscar entrada de directorio conteniendo nombre de persona. */ printf ( "Buscando dirección de %s\n", name ); while ( 1 ) { /* Devuelve una cadena de longitud cero si la búsqueda falla */ if ( fgets ( buffer, BUFFER_SIZE, file_p ) == NULL ) { *address = '\0'; break; } /* Devuelve una dirección de persona si la búsqueda tiene éxito */ if ( strstr ( buffer, name ) != NULL ) { buffer[ strlen ( buffer ) - 1 ] = '\0'; strcpy ( address, &buffer[ strlen ( name ) + 1 ] ); break; } } /* Limpia y finaliza. */ fclose ( file_p ); return; } /* Declara e inicializa la tabla EPV del gestor de búsqueda de direcciones */ look_v1_0_epv_t epv_addr = { lookup_addr };
El código de inicialización para el
servidor de números de teléfono y para el servidor
de direcciones es muy similar.
La parte del cliente es donde se utilizan los hilos. Las llamadas DCE Threads son usadas de forma explícita.
El cliente empleará los siguientes hilos :
Cuando el cliente comienza, obtendrá información concurrentemente de ambos servidores usando dos hilos y un enlace explícito. Los handles del enlace serán colocados en variables globales. Tan pronto como estos hilos terminan, el hilo principal se une con ellos y comienzan el hilo de entrada y los hilos de búsqueda.
Cuando hay una nueva entrada, los hilos de búsqueda localizarán de los servidores la información necesaria, con cada hilo especificando el handle de enlace apropiado para su servidor. Cada hilo visualiza el resultado de su búsqueda, empleando un mútex para evitar mezclar la salida de ambos hilos. Si un nombre no es encontrado se visualiza un mensaje al respecto.
Los hilos de búsqueda son sincronizados con
los hilos de entrada mediante variables de condición. Esto
permite que los hilos conozcan cuando un nombre nuevo está
disponible. También permite que el hilo de entrada espere
por un nombre anterior para que sea procesado antes de obtener
otro nombre. Cuando se introduce una cadena nula, el hilo de entrada
cancela los hilos de búsqueda. El mútex de la variable
de condición es desbloqueado por el hilo de entrada, que
entonces duerme y desbloquea el mútex de la variable de
condición de nuevo, evitando la cancelación de los
hilos de búsqueda para ser asesinados por su wait
en un bloqueo del mútex. El hilo de entrada existe. El
hilo principal se junta con los hilos que finalizan, y se presenta
un mensaje de despedida.
El ejemplo del cliente es el siguiente :
/******************************************************************************/ /* Módulo : lookup.c */ /* Propósito: Módulo cliente para los servidores phon_s y addr_s. Obtiene un */ /* nombre de persona de la entrada estándar y visualiza el número de*/ /* teléfono y la dirección en la salida estándar. El programa */ /* termina introduciendo una cadena nula. Utiliza hilos, */ /* objetos UUIDs y enlace explícito. */ /******************************************************************************/ #ifdef IBMOS2 #define INCL_DOSPROCESS #include <os2.h> #endif #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <dce/rpc.h> #ifdef _WINDOWS #include <dce/dcewin.h> #endif #include "errchk.h" #include "look.h" #define PHON_ENTRY_NAME "/.:/Servers/Phon" /* Nombre de entrada de teléfonos */ #define ADDR_ENTRY_NAME "/.:/Servers/Addr" /* Nombre de entrada de direcciones*/ #define APP_EXIT 3 #define APP_LOOKUP 2 #define THRCHK( x ) \ if ( ( x ) == -1 ) { \ printf ( "ERROR DEL HILO: %s:%d\n", __FILE__, __LINE__ ); \ exit ( 1 ); \ } /*static void get_bind ( char **, char *, char *, rpc_binding_handle_t * );*/ pthread_t phon_thread_h, /* Handle del hilo de télefonos. */ addr_thread_h, /* Handle del hilo de direcciones. */ addr_bind_thread_h, phon_bind_thread_h; rpc_binding_handle_t phon_bh, /* Handle de enlace del servidor de teléfonos */ addr_bh; /* Handle de enlace del servidor de direcciones*/ /* Mútex para la variable de condición name_ready. */ pthread_mutex_t name_mutex; /* Predicado para la variable de condición name_ready. */ int name_ready_p; /* Variable de condición variable para acceder al nombre. */ pthread_cond_t name_ready; char **argv; name_t name; /* Nombre de la persona */ dir_entry_t phone, /* Número de teléfono de la persona */ address; /* Dirección de la persona */ /******************************************************************************/ /* Procedimiento : main */ /* Propósito : Programa principal del módulo cliente. Obtiene información de */ /* enlace para el servidor del directorio de teléfonos y el */ /* servidor del directorio de direcciones, entonces crea un hilo */ /* para hacer una búsqueda en el directorio de teléfonos y crea */ /* otro hilo para hacer una búsqueda en el directorio de */ /* direcciones en los servidores. */ /******************************************************************************/ int main ( int argc, char *argvv[] ) { int status; void look_thread (), get_person (), get_bind(); argv = argvv; /* Obtiene información de enlace del servidor de teléfonos. */ status = pthread_create ( &phon_bind_thread_h, pthread_attr_default, ( pthread_startroutine_t )get_bind, ( pthread_addr_t ) 1 ); THRCHK ( status ); /* Obtiene información de enlace del servidor de direcciones. */ get_bind ( 2 ); /* Espera a que terminen todos los hilos enlazados. */ status = pthread_join ( phon_bind_thread_h, NULL ); THRCHK ( status ); /* Libera el espacio que estaba siendo usado por los hilos enlazados. */ status = pthread_detach ( &phon_bind_thread_h ); THRCHK ( status ); /* Inicializa el mutex de la variable de condición */ status = pthread_mutex_init ( &name_mutex, pthread_mutexattr_default ); THRCHK ( status ); /* Inicializa la variable de condición */ status = pthread_cond_init ( &name_ready, pthread_condattr_default ); THRCHK ( status ); /* Inicializa el predicado de la variable de condición */ name_ready_p = APP_LOOKUP; /* Crea un hilo para buscar un número de teléfono en el servidor. */ status = pthread_create ( &phon_thread_h, pthread_attr_default, ( pthread_startroutine_t )look_thread, ( pthread_addr_t ) 1 ); THRCHK ( status ); /* Crea un hilo para buscar una dirección en el servidor. */ status = pthread_create ( &addr_thread_h, pthread_attr_default, ( pthread_startroutine_t )look_thread, ( pthread_addr_t ) 2 ); THRCHK ( status ); /* Obtiene un nombre de persona de la entrada. */ get_person ( ); /* Espera a que finalicen los hilos restantes. */ status = pthread_join ( phon_thread_h, NULL ); THRCHK ( status ); status = pthread_join ( addr_thread_h, NULL ); THRCHK ( status ); /* Libera el espacio que estaba siendo usado por los hilos restantes. */ status = pthread_detach ( &phon_thread_h ); THRCHK ( status ); status = pthread_detach ( &addr_thread_h ); THRCHK ( status ); printf ( "Gracias por usar %s !\n", argv[ 0 ] ); } /******************************************************************************/ /* Procedimiento : get_person */ /* Propósito : Rutina para obtener el nombre de una persona de stdin. */ /* Comunica con los hilos look_phon y look_addr usando la variable*/ /* global "name" controlada por la variable de condición */ /* name_ready y su predicado name_ready_p. La rutina finaliza */ /* introduciendo una cadena nula. */ /******************************************************************************/ void get_person () { int status; /* Bloquea el mutex de la variable de condición. */ status = pthread_mutex_lock ( &name_mutex ); THRCHK ( status ); /* Espera por la variable de condición hasta que los hilos están listos */ while ( name_ready_p ) { status = pthread_cond_wait ( &name_ready, &name_mutex ); THRCHK ( status ); }; status = pthread_mutex_unlock ( &name_mutex ); THRCHK ( status ); /* Bucle hasta que se introduce una cadena nula */ while ( 1 ) { /* Visualiza un prompt. */ printf ( "\nIntroduzca el nombre o una cadena nula para finalizar :\n" ); /* Obtener otro nombre, saliendo del bucle si se introuce una cadena nula*/ memset ( name, '\0', MAX_NAME_LENGTH + 1 ); gets ( name ); /* Bloquea el mutex de la variable de condición. */ status = pthread_mutex_lock ( &name_mutex ); THRCHK ( status ); /* Actualiza el predicado e indica a todos la actualización. */ name_ready_p = ( *name == '\0' ) ? APP_EXIT : APP_LOOKUP; status = pthread_cond_broadcast ( &name_ready ); THRCHK ( status ); /* Si se introdujo una cadena nula, se sale. */ if ( name_ready_p == APP_EXIT ) { status = pthread_mutex_unlock ( &name_mutex ); THRCHK ( status ); break; } /* Espera por una variable de condición hasta que ambos hilos finalizan */ /* la búsqueda. */ do { status = pthread_cond_wait ( &name_ready, &name_mutex ); THRCHK ( status ); } while ( name_ready_p ); status = pthread_mutex_unlock ( &name_mutex ); THRCHK ( status ); /* Imprimir las búsquedas. */ if ( *phone != '\0' ) printf ( "Número de teléfono: %s\n", phone ); else printf ( "El número de teléfono de %s no ha sido encontrado\n", name ); if ( *address != '\0' ) printf ( "Dirección: %s\n", address ); else printf ( "Dirección de %s no ha sido encontrado\n", name ); } } /******************************************************************************/ /* Procedimiento: look_thread */ /* Propósitos : Rutina de hilo para hacer una búsqueda en un servidor de */ /* directorios de teléfono o de direcciones usando un nombre de */ /* persona. El nombre de persona es obtenido de una variable */ /* global "name" controlada por la variable de condición */ /* name_ready y su predicado name_ready_p. El resultado es */ /* visualizado en stdout. */ /******************************************************************************/ void look_thread (int type) { int status; /* Ejecución haste ser cancelado. */ while ( 1 ) { /* Bloquea el mutex de la variable de condición. */ status = pthread_mutex_lock ( &name_mutex ); THRCHK ( status ); /* Flag para indicar que un acceso ha sido completado. */ name_ready_p--; /* Si todos los accesos han sido completados, se indica a todos. */ if ( name_ready_p == 0 ) { status = pthread_cond_broadcast ( &name_ready ); THRCHK ( status ); } /* Esperar poe una variable de condición hasta que haya un nombre. */ do { status = pthread_cond_wait ( &name_ready, &name_mutex ); } while ( name_ready_p <= 1 ); /* Liberar el mutex de la variable de condición. */ status = pthread_mutex_unlock ( &name_mutex ); THRCHK ( status ); if ( name_ready_p == APP_EXIT ) break; /* Realizar una búsqueda seleccionando el servidor correcto a través de */ /* su handle. */ if (type == 1) { lookup_dir ( phon_bh, name, phone ); } else { lookup_dir ( addr_bh, name, address ); } } } /******************************************************************************/ /* Procedimiento : get_bind */ /* Propósito : Rutina de hilo para obtener la información de enlace del */ /* servidor de direcciones y del servidor de teléfonos. */ /******************************************************************************/ void get_bind (int server_type) { uuid_t obj_uuid; unsigned32 status; rpc_ns_handle_t imp_ctxt; char *obj_uuid_string; char *entry_name; rpc_binding_handle_t *binding_handle; if (server_type == 1) { printf ( "Obteniendo handle de enlace del servidor de teléfonos\n" ); obj_uuid_string = PHON_OBJ_UUID; entry_name = PHON_ENTRY_NAME; binding_handle = &phon_bh; } else { printf ( "Obteniendo handle de enlace del servidor de direcciones\n" ); obj_uuid_string = ADDR_OBJ_UUID; entry_name = ADDR_ENTRY_NAME; binding_handle = &addr_bh; } /* endif */ /* Crea objeto UUID para el directorio de teléfonos de la cadena. */ uuid_from_string ( obj_uuid_string, &obj_uuid, &status ); ERRCHK ( status ); /* Inicializa el contexto para importar enlaces del servidor de teléfonos. */ rpc_ns_binding_import_begin ( rpc_c_ns_syntax_dce, entry_name, look_v1_0_c_ifspec, &obj_uuid, &imp_ctxt, &status ); ERRCHK ( status ); /* Obtiene el primer handle de enlace del servidor telefónico. */ rpc_ns_binding_import_next ( imp_ctxt, binding_handle, &status ); ERRCHK ( status ); /* Linrear el contexto. */ rpc_ns_binding_import_done ( &imp_ctxt, &status ); ERRCHK ( status ); if (server_type == 1) { printf ( "Enlace con el servidor de teléfonos realizado\n" ); } else { printf ( "Enlace con el servidor de direcciones realizado\n" ); } /* endif */ }