Solaris 2 fue el primer sistema de Sun Microsystems que ofrecía un kernel con multiproceso simétrico basado en hilos. Aunque los hilos de nivel kernel estaban disponibles desde Solaris 2.0, la biblioteca de hilos de nivel usuario no fue incluida hasta la versión Solaris 2.1 en Septiembre de 1992, que incluía un borrador del paquete final. Fue en Junio de 1993 con Solaris 2.2, cuando la biblioteca de hilos de usuario se entregó como parte integral del sistema Solaris 2.2, adoptando el estándar UI Threads.
El sistema operativo SunOS 5.3 del sistema Solaris 2.3 tiene un paquete de hilos de nivel usuario también conocido como UI Threads o Unix International Threads (también soportado por UnixWare 2 de SCO). El paquete implementa hilos de nivel usuario, pero tienen un soporte a nivel del kernel
A continuación se hará una descripción
general del paquete para el programador a nivel usuario, así
como las herramientas que proporciona el paquete para programar
aplicaciones multihilo en el entorno Solaris.
En la arquitectura multihilo de SunOS 5.3, los hilos son abstracciones implementadas por el paquete o biblioteca de hilos. Los hilos de Solaris Threads son implementados como una biblioteca que se apoya en hilos de control, llamados procesos ligeros. La biblioteca controla la planificación de los hilos de usuario apoyándose para ello sobre procesos ligeros o LWPs, que son entidades de ejecución independientes dentro de un mismo proceso y son soportadas por el kernel. Esto permite que existan cientos de hilos dentro de un proceso, mientras que el número de procesos ligeros esté reducido a las necesidades actuales de concurrencia de los recursos del sistema. La biblioteca planifica hilos de usuario, mientras que el kernel planifica LWPs.
Los hilos son suficientemente ligeros por lo que pueden ser creados rápidamente, puede haber miles de ellos, y la sincronización se puede realizar rápidamente. Estos objetivos se alcanzan proporcionando hilos de nivel usuario que se multiplexan en un conjunto de procesos ligeros o LWPs. Este conjunto es gestionado por la biblioteca y crecerá o decrecerá según sea necesario para asegurar que el proceso progrese, siempre y cuando no se emplee un excesivo número de recursos kernel. El programador también puede modificar la relación entre hilos usuario y procesos ligeros.
Existen dos interfaces de programación de cara al usuario que proporciona el paquete de hilos, para cada uno de los dos niveles de la arquitectura :
Cada hilo es representado por una estructura de hilo que contiene el identificador de hilo, un área para almacenar el contexto de la ejecución del hilo, la máscara de señales del hilo, la prioridad del hilo, y un puntero a la pila del hilo. El espacio de almacenamiento para la pila es creado por la biblioteca o es pasado por la aplicación durante la creación del hilo. El espacio para las pilas que proporciona la biblioteca se obtiene mediante páginas de memoria anónimas. Así, la biblioteca garantiza que la página siguiente a la pila es inválida, esto representa una "zona roja" de forma que si un hilo intenta ejecutarse fuera de la pila es indicado al proceso mediante una señal. Si la aplicación proporciona su propio espacio de pila, puede proporcionar una "zona roja" o empaquetar las pilas en su propio espacio.
Cuando se crea un hilo, se le asigna un identificador
de hilo ID, que es un índice de una tabla de punteros a
estructuras de hilo. Esto permite a la biblioteca gestionar los
errores cuando se le pasa un ID de un hilo finalizado y liberado.
La biblioteca de hilos implementa un planificador de hilos que mezcla la ejecución de hilos entre un conjunto de LWPs. Cualquier hilo puede ejecutarse sobre cualquier LWP del conjunto. Cuando se ejecuta un hilo de usuario, es enlazado a un LWP y tiene todos los atributos para ser un hilo de nivel kernel.
Cuando un LWP del conjunto está desocupado
espera en una variable de sincronización a que se le asigne
trabajo. Cuando un hilo, T1 se convierte en ejecutable, es añadido
a la cola de ejecutables, y un LWP desocupado L1 (si existe alguno
en este estado) del conjunto es despertado a través de
su variable de sincronización. El LWP L1 se despierta y
selecciona al hilo de mayor prioridad de la cola de ejecutables.
Si T1 se bloquea en un objeto de sincronización local (no
compartido entre procesos), L1 pone a T1 en la cola de dormidos
y conmuta al hilo de mayor prioridad en la cola de ejecutables.
Si la cola de ejecutables está vacía, el LWP se
convierte en desocupado. Si todos los LWPs en el conjunto están
ocupados cuando T1 se convierte en ejecutable, T1 simplemente
permanece en la cola de ejecutables, esperando a que un LWP esté
disponible. Un LWP está disponible cuando se añade
un nuevo LWP al conjunto o cuando uno de los hilos en ejecución
se bloquea en una variable de sincronización local, finaliza
o se detiene, liberando su LWP.
La biblioteca implementa dos tipos básicos de variables de sincronización : locales al proceso, USYNC_THREAD (por defecto), y compartidas entre procesos, USYNC_PROCESS. En ambos casos la biblioteca garantiza que las primitivas de sincronización sean seguras con respecto a señales asíncronas y puedan ser llamadas desde gestores de señales.
La acción por defecto es poner el hilo a dormir. Cada variable de sincronización tiene una cola de hilos dormidos asociada. Las primitivas de sincronización colocan los hilos bloqueados en la cola de dormidos de la variable de sincronización y entregan la ejecución del hilo al planificador. Si el hilo no está enlazado, el planificador selecciona otro hilo para ser ejecutado sobre el LWP. Un hilo enlazado, permanece enlazado a su LWP, y por lo tanto el LWP es suspendido en su hilo. Los hilos bloqueados son despertados cuando la variable de sincronización está disponible.
Las primitivas de sincronización que liberan los hilos bloqueados primero comprueban si los hilos esperan por las variables de sincronización. Un hilo bloqueado es eliminado de la cola de dormidos de la variable de sincronización y es seleccionado por el planificador. Si el hilo está enlazado, el planificador despierta el hilo enlazado y por lo tanto su LWP es seleccionado por el kernel. Para los hilos no enlazados, el planificador simplemente coloca el hilo en una cola de ejecución correspondiente a su prioridad. El hilo es seleccionado por un LWP según la prioridad.
Los objetos de sincronización compartidos
entre procesos pueden ser colocados en memoria compartida accesible
por más de un proceso, lo que permite sincronizar hilos
de diferentes procesos. Estas variables de sincronización
compartidas deben ser inicializadas cuando son creadas, ya que
son especiales. Cada primitiva de sincronización soporta
una función de inicialización que debe ser llamada
para marcar la variable de sincronización como compartida
entre procesos. De esta forma, las primitivas pueden reconocer
este tipo de variables y proporcionar las características
correctas de los bloqueos. Las primitivas dependen de las primitivas
de sincronización LWP para poner a los hilos bloqueados
a dormir en el kernel enlazado con sus LWPs, y sincronizar correctamente
entre procesos.
El API de hilos que se presenta aquí corresponde a la versión implementada por SunOS 5.3, conocida como UI Threads (Unix International Threads). Las versiones posteriores de SunOS, 2.4 y 2.5 siguen soportando este interfaz, pero además soportan el estándar POSIX 1003.4 (1003.1c Draft 8) y 1003.1c, respectivamente.
Tanto Solaris threads como POSIX Threads (PThreads) se parecen mucho tanto en la sintaxis como en la funcionalidad de su API, salvo que los nombres de función son diferentes, aunque fácilmente relacionables (muchas veces basta cambiar el prefijo pthread_ de una función PThreads por el prefijo thr_ para convertirla en función de Solaris Threads. No existe una correspondencia exacta entre los dos APIs, ya que PThreads incluye funciones no soportadas por el interfaz Solaris, y viceversa. Las principales diferencias entre ambos son :
1) Características en Solaris Threads pero no en PThreads :
2) Características en PThreads per no en Solaris Threads :
La biblioteca de hilos Solaris Threads está compuesta por 40 funciones, que se encuentran en los siguientes ficheros de cabecera :
Para compilar se debe emplear la directiva _REENTRANT.
cc -D_REENTRANT ... -lthread
Casi todas las funciones devuelven un código de error en caso de fallo y establecen el error en la variable errno, al igual que se especifica en el estándar POSIX.1c. A continuación se describe el API de Solaris Threads (UI-Threads).
Estas funciones son básicas para la creación, gestión y destrucción de hilos.
#include <thread.h> int thr_create(void *stkaddr, size_t stksize, void *(*func)(void *), void *arg, long flags, thread_t *tid)
- THR_BOUND : Crea un hilo enlazado permanentemente a un LWP.
- THR_NEW_LWP : Crea un nuevo LWP al crear el hilo, incrementando el grado de concurrencia.
- THR_DETACHED : Crea un hilo independiente.
- THR_SUSPENDED : Crea un hilo suspendido tras ser creado.
- THR_DAEMON : Crea un hilo como "daemon".
Esta función crea un hilo con las características
especificadas. Con todos los valores por defecto, el hilo es creado
sin enlazar de forma fija a un LWP, no independiente, con tamaño
y dirección de pila seleccionados por la biblioteca, y
hereda la prioridad del padre.
#include <thread.h> int thr_join(thread_t tid, thread_t *departedid, int *status)
Suspende el hilo actual hasta que finaliza la ejecución
del hilo especificado por tid, devolviendo
el estado de salida del mismo en status
si se desea.
#include <thread.h> #include <signal.h> void thr_exit(int *status)
Finaliza el hilo que realiza la llamada y devuelve el estado indicado.
thread_t thr_self()
Devuelve el identificador del hilo que realiza la llamada.
int thr_yield()
Otorga voluntariamente el quantum de ejecución
asignado al hilo que realiza la llamada a otro hilo de la misma
prioridad.
Cada hilo tiene su propia máscara de señales. La máscara de señales se aplica tanto para las señales enviadas por hilos del mismo proceso como las enviadas por hilos de otros procesos. Aunque el resto de funciones siguen siendo utilizables por un hilo, se definen las siguientes funciones especiales para gestión de señales de hilo :
#include <thread.h> #include <signal.h> int thr_sigsetmask(int how, const sigset_t *new, sigset_t *old)
- SIG_SETMASK : Establece una nueva máscara.
- SIG_BLOCK : Bloquea la máscara.
- SIG_UNBLOCK : Desbloquea la máscara.
Realiza la acción especificada sobre la máscara
de señales del hilo.
#include <thread.h> #include <signal.h> int thr_kill(thread_t tid, int sig)
Envía la señal indicada por sig al hilo especificado por tid.
Al igual que en PThreads, Solaris Threads presenta una serie de funciones para gestionar los datos locales a cada hilo, pero globales dentro del mismo.
#include <thread.h> int thr_keycreate(thread_key_t *keyp, void (*destructor)(void *)) int thr_setspecific(thread_key_t key, void *value) int thr_getspecific(thread_key_t key, void **value)
Solaris Threads define una serie de funciones que permiten controlar la prioridad de los hilos de usuario.
#include <thread.h> int thr_getprio(thread_t tid, int *prio) int thr_setprio(thread_t tid, int prio)
Estas funciones permiten obtener o establecer la prioridad del hilo especificado.
La gestión de mútex es similar a la que se especifica en el estándar POSIX.1c, y además su sintaxis es bastante parecida, salvo la inicialización. Los mútex proporcionan exclusión mutua. Generan poca sobrecarga de espacio y tiempo, y son de uso frecuente. Se emplean para evitar inconsistencia de datos en secciones críticas de código. También se pueden emplear para ejecutar código que no funciona en multihilo.
#include <synch.h> int mutex_init(mutex_t *mp, int type, void *arg) int mutex_destroy(mutex_t *mp) int mutex_lock(mutex_t *mp) int mutex_trylock(mutex_t *mp) int mutex_unlock(mutex_t *mp)
- USYNC_THREAD : El mútex es local a los hilos del proceso.
- USYNC_PROCESS : El mútex es global a todos los hilos de todos los procesos.
Este grupo de funciones permiten la inicialización, destrucción, adquisición, intento de adquisición y liberación de un mútex, respectivamente.
La gestión de variables de condición es similar a la que se especifica en el estándar POSIX.1c, y además su sintaxis es bastante parecida, salvo la inicialización. Las variable de condición son empleadas para esperar hasta que se verifique una condición particular. Deben ser empleadas junto con un mútex bloqueado. De esta forma se implementa la estructura típica de monitor.
#include <synch.h> int cond_init(cond_t *cv, int type, int arg) int cond_destroy(cond_t *cv) int cond_wait(cond_t *cv, mutex_t *mp) int cond_timedwait(cond_t *cv, mutex_t *mp, timestruct_t abstime) int cond_signal(cond_t *cv) int cond_broadcast(cond_t *cv)
- USYNC_THREAD : La variable de condición es local a los hilos del proceso.
- USYNC_PROCESS : La variable de condición es global a todos los hilos de todos los procesos.
Este grupo de funciones permiten realizar las siguientes funciones sobre una variable de condición, respectivamente : la inicialización, destrucción, espera, espera temporal, indicación a un hilo en espera e indicación a todos los hilos en espera de la variable de condición.
La gestión de semáforos es similar a la que se especifica en el borrador 1003.4a, pero que finalmente no se incluyó en el estándar POSIX 1003.1c. Las facilidades de sincronización mediante semáforos proporcionan semáforos contadores clásicos. No son tan eficientes como los mútex, pero pueden ser empleados para notificación de eventos asíncronos, como los gestores de señales.
#include <synch.h> int sema_init(sema_t *sp, unsigned int count, int type, void *arg) int sema_destroy(sema_t *sp) int sema_wait(sema_t *sp) int sema_trywait(sema_t *sp) int cond_post(sema_t *sp)
- USYNC_THREAD : El semáforo es local a los hilos del proceso.
- USYNC_PROCESS : El semáforo es global a todos los hilos de todos los procesos.
Este grupo de funciones permiten realizar las siguientes funciones sobre un semáforo, respectivamente : la inicialización, destrucción, decremeto (operación P de Dijkstra), intento de decremento del contador (operación P de Dijkstra sin bloqueo), e incremento (operación V de Dijkstra).
La gestión de bloqueos de lectores/escritor es una características nueva de Solaris Threads que no está implementada en el estándar POSIX. Los bloqueos de múltiples lectores - un sólo escritor, permiten que varios hilos realicen accesos de sólo lectura simultáneos a un objeto protegido por el bloqueo, de forma que sólo un hilo puede realizar un acceso para escritura al objeto en un momento dado, y garantiza la exclusión entre cualquiera de los lectores y el escritor. Un buen ejemplo, es un registro de una base de datos, que es frecuentemente accedido para consulta, y en ocasiones es modificado. Este bloqueo se suele conocer con el nombre de bloqueo de lectores/escritores.
#include <thread.h> int rwlock_init(rwlock_t *rwlp, int type, void *arg) int rwlock_destroy(rwlock_t *rwlp) int rw_rdlock(rwlock_t *rwlp) int rw_wrlock(rwlock_t *rwlp) int rw_unlock(rwlock_t *rwlp) int rw_tryrdlock(rwlock_t *rwlp) int rw_trywrlock(rwlock_t *rwlp)
- USYNC_THREAD : El bloqueo es local a los hilos del proceso.
- USYNC_PROCESS : El bloqueo es global a todos los hilos de todos los procesos.
Este grupo de funciones permiten realizar las siguientes funciones sobre un semáforo, respectivamente : la inicialización, destrucción, bloqueo para lectura, bloqueo para escritura, liberación del bloqueo adquirido, intento de bloqueo para lectura, intento de bloqueo para escritura.
Existen otra serie de funciones que soporta Solaris y no soporta PThreads, y que permiten un mayor control sobre los hilos.
#include <thread.h> int thr_suspend(thread_t tid) int thr_continue(thread_t tid)
Estas funciones permiten la suspensión y reanudación
del hilo especificado.
#include <thread.h> int thr_setconcurrency(int new_level) int thr_getconcurrency(void)
Estas funciones permiten establecer y obtener el
nivel de concurrencia de la aplicación. El sistema se encarga
de garantizar que exista un número suficiente de hilos
activos para que el proceso continúe progresando.
Las siguientes funciones permiten la creación de procesos hijos.
int fork() int fork1()
La función fork()
crea un proceso hijo, duplicando todos los hilos del proceso padre
en el proceso hijo. En PThreads sólo se duplica en el proceso
hijo el hilo que realiza la llamada, de forma similar a la llamada
fork1() de Solaris.
En Solaris se pueden emplear conjuntamente o independientemente los dos tipos de API, Solaris Threads y PThreads, de la siguiente forma :
cc -D_REENTRANT fuente.c -lthread -lpthread (o -lposix4 según nivel de estándar)
A continuación se presenta un cuadro que resume el interfaz de programación de hilos según POSIX 1003.1c y según UI-Threads (o Solaris Threads), de forma que se pueden observar las funciones equivalentes en ambos entornos, y aquellas que sólo se encuentran en uno de ellos.
pthread_create() |
thr_create() |
pthread_exit() |
thr_exit() |
pthread_join() |
thr_join() |
pthread_yield() |
thr_yield() |
pthread_self() |
thr_self() |
pthread_kill() |
thr_kill() |
pthread_sigmask() |
thr_sigsetmask() |
pthread_setschedparam() |
thr_setprio() |
pthread_getschedparam() |
thr_getprio() |
|
thr_setconcurrency() |
|
thr_getconcurrency() |
|
thr_suspend() |
|
thr_continue() |
pthread_key_create() |
thr_keycreate() |
pthread_key_delete() |
|
pthread_setspecific() |
thr_setspecific() |
pthread_getspecific() |
thr_getspecific() |
pthread_once() |
|
pthread_equal() |
|
pthread_cancel() |
|
pthread_testcancel() |
|
pthread_cleanup_push() |
|
pthread_cleanup_pop() |
|
pthread_setcanceltype() |
|
pthread_setcancelstate() |
|
pthread_mutex_lock() |
mutex_lock() |
pthread_mutex_unlock() |
mutex_unlock() |
pthread_mutex_trylock() |
mutex_trylock() |
pthread_mutex_init() |
mutex_init() |
pthread_mutex_destroy() |
mutex_destroy() |
pthread_cond_wait() |
cond_wait() |
pthread_cond_timedwait() |
cond_timedwait() |
pthread_cond_signal() |
cond_signal() |
pthread_cond_broadcast() |
cond_broadcast() |
pthread_cond_init() |
cond_init() |
pthread_cond_destroy() |
cond_destroy() |
|
rwlock_init() |
|
rwlock_destroy() |
|
rw_rdlock() |
|
rw_wrlock() |
|
rw_unlock() |
|
rw_tryrdlock() |
|
rw_trywrlock() |
sem_init() POSIX 1003.4 |
sema_init() |
sem_destroy() POSIX 1003.4 |
sema_destroy() |
sem_wait() POSIX 1003.4 |
sema_wait() |
sem_post() POSIX 1003.4 |
sema_post() |
sem_trywait() POSIX 1003.4 |
sema_trywait() |
pthread_mutex_setprioceiling() |
|
pthread_mutex_getprioceiling() |
|
pthread_mutexattr_init() |
|
pthread_mutexattr_destroy() |
|
pthread_mutexattr_setpshared() |
Argumento type en cond_init() |
pthread_mutexattr_getpshared() |
|
pthread_mutexattr_setprioceiling() |
|
pthread_mutexattr_getprioceiling() |
|
pthread_mutexattr_setprotocol() |
|
pthread_mutexattr_getprotocol() |
|
pthread_condattr_init() |
|
pthread_condattr_destroy() |
|
pthread_condattr_getshared() |
|
pthread_condattr_setshared() |
Argumento type en cond_init() |
pthread_attr_init() |
|
pthread_attr_destroy() |
|
pthread_attr_getscope() |
|
pthread_attr_setscope() |
Flag THR_BOUND en thr_create() |
pthread_attr_getstacksize() |
|
pthread_attr_setstacksize() |
Argumento stksize en thr_create() |
pthread_attr_getstackaddr() |
|
pthread_attr_setstackaddr() |
Argumento stkaddr en thr_create() |
pthread_attr_getdetachstate() |
|
pthread_attr_setdetachstate() |
Flag THR_DETACH en thr_create() |
pthread_attr_getschedparam() |
|
pthread_attr_setschedparam() |
|
pthread_attr_getinheritsched() |
|
pthread_attr_setinheritsched() |
|
pthread_attr_getschedpolicy() |
|
pthread_attr_setschedpolicy() |
|
A continuación vamos a mostrar un ejemplo simple, codificado en lenguaje C empleando el API de Solaris Threads. Como se puede observar la utilización de los hilos de Solaris es muy similar a la que se hace con otros paquetes de hilos. Basta con saber la sintaxis para el API concreto que se programa, aunque a veces existen algunas diferencias sutiles.
El ejemplo corresponde al clásico problema
del Productor/Consumidor, limitándonos a un productor y
un consumidor que emplean un buffer circular compartido. El código
del ejemplo es el siguiente :
/* Para indicar que se empleen las bibliotecas reentrantes hilo-seguras se emplea el siguiente DEFINE */ #define _REEENTRANT #include <stdio.h> #include <thread.h> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/uio.h> #define BUFSIZE 512 /* Tamño del buffer */ #define BUFCNT 4 /* Número de buffers */ /* Estructura de datos común al productor y al consumidor */ struct { char buffer[BUFCNT][BUFSIZE]; /* Buffers de datos */ int nbytesbuf[BUFCNT]; /* No. de bytes en los buffers */ mutex_t buflock; /* Mutex de acceso a los buffers */ mutex_t finlock; /* Mutex de indicación de fin */ cond_t adddata; /* Var.cond. para añadir datos */ cond_t remdata; /* Var.cond. para extraer datos */ int in; /* Indice de entrada de datos */ int out; /* Indice de salida de datos */ int nocup /* No. de buffers ocupados */ int fin; /* Indicador de fin */ } Buf; /* Función del consumidor */ void *Consumidor(void *); main(int argc, char **argv) { int ifd, ofd; /* Descriptores de los ficheros de entrada y salida */ thread_t hilo_cons; /* Hilo del consumidor */ /* Comprueba la línea de argumentos */ if (argc != 3) { printf("Sintax: %s <ficEnt> <ficSal >\n", argv[0]); exit(0); } /* Abre el fichero de entrada para que lea el productor */ if ((ifd = open(argv[1], O_RDONLY)) == -1) { fprintf(stderr, "No se puede abrir el fichero %s\n", argv[1]); exit(1); } /* Abre el fichero de salida para que escriba el consumidor */ if ((ofd = open(argv[2], O_WRONLY|O_CREAT, 0666)) == -1) { fprintf(stderr, "No se puede abrir el fichero %s\n", argv[2]); exit(1); } /* Inicializa los contadores */ Buf.in = Buf.out = Buf.nocup = Buf.fin = 0; /* Establece el nivel de concurrencia a 2, de forma que el productor y el consumidor se puedan ejecutar concurrentemente */ thr_setconcurrency(2); /* Crea el hilo consumidor */ thr_create(NULL, 0, Consumidor, (void *)ofd, NULL, &hilo_cons); /* El hilo padre asume el papel del Productor */ while (1) { /* Bloqueo el mutex de acceso al buffer */ mutex_lock(&Buf.buflock); /* Mientras el buffer esté lleno, esperar */ while (Buf.nocup == BUFCNT) cond_wait(&Buf.remdata, &Buf.buflock); /* Leer los datos del fichero al buffer */ Buf.nbytesbuf[Buf.in] = read(ifd,Buf.buffer[Buf.in],BUFSIZE); /* Comprobar si se ha finalizado, porque no hay más datos para leer */ if (Buf.nbytesbuf[Buf.in] == 0) { /* Bloquea el mutex para acceder al indicador de fin */ mutex_lock(&Buf.finlock); /* Establece el indicador de fin a cierto */ Buf.fin = 1; /* Libera el mutex de acceso al indicador de fin */ mutex_unlock(&Buf.finlock); /* Indicar al Consumidor que existen datos en un buffer */ cond_signal(&Buf.adddata); /* Libera el mutex de acceso al buffer */ mutex_unlock(&Buf.buflock); /* Finaliza el bucle */ break; } /* Cálcula el índice del siguiente buffer a llenar */ Buf.in = ++Buf.in % BUFCNT; /* Incrementa el número de buffers llenos */ Buf.nocup++; /* Indica al Consumidor que existen datos en un buffer */ cond_signal(&Buf.adddata); /* Libera el mutex de acceso al buffer */ mutex_unlock(&Buf.buflock); } /* Cierra el fichero de entrada */ close(ifd); /* Espera a que finalice el Consumidor */ thr_join(hilo_cons, 0, NULL); /* Finaliza el programa */ return(0); } /* El Consumidor */ void *consumer(void *arg) { int fd = (int) arg; /* Descriptor del fichero de salida */ while (1) { /* Bloqueo el mutex de acceso al buffer */ mutex_lock(&Buf.buflock); /* Si no hay más datos y el indicador de fin está activado, finaliza */ if (!Buf.nocup && Buf.fin) { /* Libera el mutex de acceso al buffer */ mutex_unlock(&Buf.buflock); /* Finaliza */ break; } /* Mientras no hay datos y no se indique la finalización, espero */ while (Buf.nocup == 0 && !Buf.fin) cond_wait(&Buf.adddata, &Buf.buflock); /* Escribir los datos del buffer al fichero */ write(fd, Buf.buffer[Buf.out], Buf.nbytesbuf[Buf.out]); /* Calcula el indice del siguiente buffer a leer */ Buf.out = ++Buf.out % BUFCNT; /* Decrementa el número de buffers llenos */ Buf.nocup--; /* Indica al Productor que existe un buffer liber */ cond_signal(&Buf.remdata); /* Libera el mutex de acceso al buffer */ mutex_unlock(&Buf.buflock); } /* Finaliza el hilo */ thr_exit((void *)0); }
Todos los datos relativos al rendimiento que se presenta a continuación fueron tomados en una SPARCstation 1+ (Sun 4/65), que es una plataforma de SPARC a 25Mhz. No son datos concluyentes, pero dan una idea de la potencia de los hilos. Las medidas se obtuvieron en microsegundos, empleando un sistema prototipo sin optimizar.
La primera medida corresponde al tiempo de creación de un hilo. Mide el tiempo consumido en crear un hilo empleando un pila por defecto que se aloja en la cache del paquete de hilos. Este tiempo solo incluye el tiempo de creación actual, no incluye el tiempo del cambio de contexto inicial al hilo.
Creación de un hilo no enlazado. | |
Creación de un hilo enlazado a LWP. |
Las medidas fueron tomadas para la creación de hilos ligados (o enlazados) y no ligados a LWPs. La creación de hilos enlazados conlleva una llamada al kernel para crear también un proceso ligero (LWP) para ejecutar el hilo de nivel usuario. La creación de un hilo sin enlazar se realiza sin intervención del kernel.
La medida del tiempo de sincronización se realizó para dos hilos que emplean variables de sincronización, según el siguiente algoritmo :
sema_t s1, s2; thread1() { ... start_timer(); sema_v(&s1); sema_p(&s2); t = end_timer(); ... } thread2() { ... sema_p(&s1); sema_v(&s2); ... }
Los tiempos presentados son el resultado de dividir por dos los tiempos reales ya que se emplean dos primitivas de sincronización.
Sincronización de hilos no enlazados. | |
Sincronización de hilos enlazados. | |
Sincronización de hilos de procesos distintos, mediante un fichero en memoria compartida. |