Está en la página 1de 35

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

CAPTULO II: Programacin con


POSIX Threads
II.1. INTRODUCCION A LA PROGRAMACION CON THREADS
Una librera o paquete de threads permite escribir programas con varios puntos
simultneos de ejecucin, sincronizados a travs de memoria compartida. Sin embargo
la programacin con hilos introduce nuevas dificultades. La programacin concurrente
tiene tcnicas y problemas que no ocurren en la programacin secuencial. Algunos
problemas son sencillos (por ejemplo el nter bloqueo) pero algunos otros aparecen
como penalizaciones al rendimiento de la aplicacin.
Un thread es un concepto sencillo: un simple flujo de control secuencial. Con un
nico thread existe en cualquier instante un nico punto de ejecucin. El programador
no necesita aprender nada nuevo para programar un nico thread.
Sin embargo, cuando se tienen mltiples hilos en un programa significa que en
cualquier instante el programa tiene mltiples puntos de ejecucin, uno en cada uno de
sus threads. El programador decide cuando y donde crear mltiples threads, ayudndose
de una librera o paquete en tiempo de ejecucin.
En un lenguaje de alto nivel, las variables globales son compartidas por todos los
threads del programa, esto es, los hilos leen y escriben en las mismas posiciones de
memoria. El programador es el responsable de emplear los mecanismos de
sincronizacin de la librera de hilos proporcionada por el lenguaje para garantizar que
la memoria compartida se acceda de forma correcta por los hilos. Las facilidades
proporcionadas por la librera de hilos que se utilice son conocidas como primitivas
ligeras, lo que significa que las primitivas de:

Creacin
Mantenimiento
Sincronizacin y
Destruccin

Son suficientemente econmicas en esfuerzo para las necesidades del programador.

II.2. EL ESTANDAR POSIX THREADS


Uno de los problemas que se tenan al utilizar mltiples threads de ejecucin es
que hasta hace poco no exista un estndar para ello. La extensin POSIX.1c se aprob
en Junio de 1995. Con la adopcin de un estndar POSIX para los hilos, se estn
haciendo ms comunes las aplicaciones con Threads.
El estndar POSIX Threads significa tcnicamente el API Thread especificado
por el estndar formal internacional POSIX 1003.1c-1995. POSIX significa: Portable
Operating System Interface. Todas las fuentes que empleen POSIX 1003.1c, POSIX.1c
o simplemente Pthreads, deben incluir el archivo de encabezado pthread.h con la
directiva:

23

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

#include <pthread.h>

Ya que pthread es una librera POSIX, se podrn portar los programas hechos
con ella a cualquier sistema operativo POSIX que soporte threads. Por tanto, para crear
programas que hagan uso de la librera pthread.h necesitamos en primer lugar la librera
en s. Esta viene en la mayora de las distribuciones de LINUX y si no es as, se puede
bajar de la red.
Una vez que tenemos la librera instalada, deberemos compilar el programa y
ligarlo con dicha librera en base al compilador que se utilice. La librera de hilos
POSIX.1c debe ser la ltima librera especificada en la lnea de comandos del
compilador:
CC . . . lpthread

Por ejemplo, la forma ms usual de hacer lo anterior, si estamos usando un


compilador GNU como gcc, es con el comando:
gcc prog_con_hilos.c o prog_con_hilos_ejecutable -lpthread

En POSIX.1c todos los hilos de un proceso comparten las siguientes caractersticas:

El espacio de direcciones
El ID del proceso
EL ID del proceso padre
El ID del proceso lder del grupo
Los identificadores de usuario
Los identificadores de grupo
El directorio de trabajo raz y actual
La mscara de creacin de archivos
La tabla de descriptores de archivos
El timer del proceso

Por otro lado, cada hilo tiene la siguiente informacin especfica:

Un identificador de hilo nico


La poltica de planificacin y la prioridad
Una variable errno por hilo
Datos especficos por hilo
Gestores de cancelacin por hilo
Mscara de seales por hilo

Las operaciones llevadas a cabo sobre un hilo son:

Creacin y destruccin
Sincronizacin entre hilos
Posibilidad de disponer para cada thread memoria local propia

24

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

Gestin de prioridades entre hilos


Gestin de seales

Como ya se ha mencionado, un proceso puede tener sus propios hilos, es decir,


los threads viven dentro de un proceso pesado, por tanto, si un thread cambia alguna de
las entidades de un proceso, el cambio ser visto por todos los threads. La funcin
main() esta asociada con el hilo principal e inicial del proceso, en otras palabras, la
funcin main() de cualquier programa que compilemos es un hilo de ejecucin.
Cualquier otro hilo de ejecucin que queramos se ha de crear explcitamente.

II.2.1. Gestin Bsica de Threads


Un thread tiene un identificador (ID), una pila, una prioridad de ejecucin y una
direccin de inicio de la ejecucin:

ID+Pila+Prioridad+Dir.Incio_Ejecucin

Thread
Hacemos referencia a los hilos POSIX mediante un ID de tipo:
pthread_t

Un hilo puede averiguar su ID llamando a:


pthread_self

II.2.2. Espacio de Nombres


A continuacin se define la estructura de los tipos de datos del estandar POSIX.1c. Cada
tipo de dato es de la forma: pthread[_object]_t

Existen 8 tipos de datos en el POSIX.1c:

25

Programacin con POSIX Threads

TIPO DE DATO
pthread_attr_t
pthread_mutexattr_t
pthread_condattr_t
pthread_mutex_t
pthread_cond_t
pthread_t
pthread_once_t
pthread_key_t

Dr. Mario Rossainz Lpez

DESCRIPCION
Atributo de hilo
Atributo de mutex
Atributo de variable de condicin
Mutex (bloqueo con exclusin mutua)
Variable de condicin
Hilo (Identificador de hilo o ID)
Ejecucin una sola vez
Clave sobre datos especficos de hilo

En cuanto a las funciones estndar del POSIX.1c, stas tienen la forma:


pthread[_object]_operation[_np_NP]
donde:
_object: es un tipo (no requerido si es thread hilo-)
_operation: es un tipo especfico de operacin
_np _NP: Es usado para identificar funciones especficas implementadas no portables
Las funciones de threads ms comunes en POSIX son:
CLASIFICACION
GESTION DE HILOS
(Existen una serie de
funciones bsicas en la
gestin de hilos que
realizan una aplicacin
multihilo)

FUNCION

DESCRIPCION
Crea un nuevo hilo de
pthread_create
ejecucin
Compara
si
dos
pthread_equal
identificadores de hilo son
el mismo hilo
Finaliza el hilo que realiza
pthread_exit
la llamada
Sincroniza el hilo actual
con la finalizacin del hilo
pthread_join
especfico, devolviendo el
estado del hilo por el que
se espera
Devuelve el identificador
pthread_self
del hilo que realiza la
llamada
Convierte
el
hilo
pthread_detach
especificado
en
independiente
Obtiene la poltica de
planificacin
y
los
pthread_getschedparam
parmetros
del
hilo
especificado
Establece la poltica de
planificacin
y
los
pthread_setschedparam
parmetros
del
hilo
especificado

26

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

pthread_kill
CLASIFICACION

EXCLUSION
MUTUA
(Un mutex es
empleado para
proteger el acceso
compartido a los
recursos)

Enva la seal especificada


al hilo especificado

DESCRIPCION
Inicializa un mutex
pthread_mutex_init
con
los
atributos
especificados
Destruye el mutex
pthread_mutex_destroy
especificado
Adquiere el mutex
pthread_mutex_lock
indicado (bloquea el
mutex)
Intenta adquirir el
pthread_mutex_trylock
mutex indicado
Libera
el
mutex
previamente
pthread_mutex_unlock
adquirido (bloqueado)
que se especifica
Obtiene el valor de
prioridad
lmite
pthread_mutex_getprioceiling
(prioceiling) del mutex
especificado
Establece el valor de
prioridad
lmite
(prioceiling)
y
pthread_mutex_setprioceiling
devuelve el anterior
valor (old-ceiling) en
el mutex especificado

CLASIFICACION

VARIABLES DE
CONDICION
(Las variables de
condicin estan
asociadas con un mutex
y se emplean para
sincronizar los hilos)

FUNCION

FUNCION

DESCRIPCION
Inicializa una variable de
pthread_cond_init
condicin con los atributos
especificados
Destruye la variable de
pthread_cond_destroy
condicin especificada
Bloquea el hilo actual a la
pthread_cond_wait
espera de la variable de
condicin especificada.
Bloquea el hilo actual a la
espera de la variable de
condicin especificada, no
pthread_cond_timewait
ms
tiempo
del
especificado en forma
absoluta por abstime.
Desbloquea
un
hilo
actualmente bloqueado en
pthread_cond_signal
la variable de condicin
especificada
pthread_cond_broadcast Desbloquea todos los hilos

27

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

actualmente bloqueados en
la variable de condicin
especificada
CLASIFICACION
EJECUCION UNA
SOLA VEZ
(Mecanismo empleado
para inicializar datos u
objetos una sola vez
para todos los hilos)

FUNCION

DESCRIPCION
Esta funcin garantiza que una nueva
llamada a la rutina no ejecutar la funcin
nuevamente. Es decir, ejecuta una sola
vez la rutina de inicializacin init_routine
pthread_once
empleando para ello la variable de
control once_control declarada:
Pthread_once_t
once_control=
PTHREAD_ONCE_INIT

CLASIFICACION

DATOS ESPECIFICOS DE
HILO
(Si un hilo desea crear datos
que no sean globales a todos
los hilos pero si a sus
procedimientos, puede crear
datos especiales de este tipo)

CLASIFICACION
CANCELACION
(La cancelacin de es
gestionada como una
peticin de envo de una
seal interna especial
SIGCANCEL a un hilo. La
accin a tomar depende del
estado de cancelacin del
hilo receptor)

FUNCION

DESCRIPCION
Crea una clave para
datos especficos de hilo,
pthread_key_create que puede ser empleada
por todos los hilos del
proceso
Destruye una clave de
pthread_key_delete
datos especficos del hilo
Establece el valor para la
pthread_setspecific clave dada en el hilo que
realiza la llamada
Devuelve el ltimo valor
para la clave dada del
pthread_getspecific
hilo que realiza la
llamada
FUNCION

DESCRIPCION
Introduce (push) una
funcin en la cima de
la pila de cancelacin,
pthread_cleanup_push
que ser ejecutada
cuando se produzca la
cancelacin del hilo
Extrae
(pop)
una
funcin de la cima de
pthread_cleanup_pop
la pila de cancelacin y
opcionalmente
la
ejecuta
Establece el estado de
cancelacin y devuelve
pthread_setcancelstate el estado anterior para
el hilo que realiza la
llamada
Establece el tipo de
pthread_setcanceltype cancelacin y devuelve

28

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

pthread_cancel

pthread_testcancel

CLASIFICACION
ATRIBUTOS DE HILO
(Los atributos permiten
crear patrones de hilo para
crear hilos con las mismas
caractersticas )
ATRIBUTOS DE MUTEX
(Los atributos permiten
crear patrones de mutex
para crear mutex con las
mismas caractersticas)
ATRIBUTOS DE
VARIABLE DE
CONDICION
(Los atributos permiten
crear patrones de variables
de condicin para crear
variables de condicin con
las mismas caractersticas )

FUNCION
pthread_attr_init
pthread_attr_destroy

el tipo anterior para el


hilo que realiza la
llamada
Cancela
el
hilo
especificado
Introduce un punto de
cancelacin que es un
punto en el que un hilo
puede ser finalizado si
se le notific una seal
de cancelacin
DESCRIPCION
Inicializa un objeto
de atributos de hilo
Destruye un objeto
de atributos de hilo

Inicializa un objeto
de atributos de
mutex
Destruye un objeto
pthread_mutexattr_destroy de atributos de
mutex
Inicializa un objeto
de atributos de
pthread_condattr_init
variable
de
condicin
Destruye un objeto
de atributos de
pthread_condattr_destroy
variable
de
condicin
pthread_mutexattr_init

CLASIFICACION

GESTION DE SEALES
(Cada hilo tiene su propia mscara
de seales. Los gestores de seales
son compartidos por todos los
hilos del proceso pues se instalan a
nivel proceso)

FUNCION

DESCRIPCION
Examina o modifica la
mscara de seales del
pthread_sigmask
hilo que realiza la
llamada
Enva
la
seal
pthread_kill
especificada
al
hilo
indicado
Acepta de forma sncrona
una seal (suspende el
sigwait
hilo hasta la llegada de la
seal indicada).

29

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

II.2.3. Creacin y Destruccin (manipulacin) de Threads


Se dice que un hilo es dinmico si se puede crear en cualquier instante durante la
ejecucin de un proceso y si no es necesario especificar por adelantado el nmero de
hilos. En POSIX los hilos se crean dinmicamente con la funcin pthread_create(), la
cual crea un hilo y lo coloca en una cola de hilos preparados.
Para crear un hilo nos valdremos de la funcin pthread_create() de la librera de
hilos POSIX y de la estructura de datos pthread_t que identifica cada thread
diferencindolo de los dems y que contiene todos sus datos:
Sintaxis:
int pthread_create(pthread_t *hilo, const pthread_attr_t *atributo,
void * (*rutina)(void *), void *arg);

Donde:
*hilo: Es una variable de tipo pthread_t que contendr los datos del thread y que nos
servir para identificar el thread en concreto cuando nos interese hacer llamadas a la
librera para llevar a cabo una accin sobre l.
*atributo: Es un parmetro del tipo pthread_attr_t que se debe inicializar previamente
con los atributos que queramos que tenga el thread. Entre los atributos estn la
prioridad, el quantum, el algoritmo de planificacin a utilizar, etc. Lo normal es pasar
como parmetro de este argumento el valor NULL para que la librera le asigne al
thread los atributos por defecto.
*rutina: Es la direccin de la funcin que se quiere que ejecute el thread. La funcin
debe devolver un apuntador genrico (void *) como resultado y debe tener como nico
parmetro otro apuntador genrico. La ventaja de tener apuntadores genricos (void *)
es que se puede devolver cualquier cosa que se nos ocurra mediante los castings de tipos
necesarios. Si necesitamos pasar o devolver ms de un parmetro a la vez, se puede
crear una estructura y meter all dentro todo lo que necesitemos. Luego pasaremos o
devolveremos la direccin de sta estructura como nico parmetro.
*arg: Es una apuntador al parmetro que se le pasar a la funcin. Puede ser NULL si
no se le quiere pasar nada a la funcin.
La funcin devuelve un cero si todo ha ido bien, o un valor distinto de cero en
caso de que haya ocurrido algn error. Una vez que se ha llamado a la funcin
pthread_create(), se tienen entonces los hilos funcionando con dos opciones:
a) Esperar a que terminen los threads, en el caso de que sea de inters recoger algn
resultado, o bien
b) Simplemente decirle a la librera de pthreads que cuando termine la ejecucin de la
funcin del thread en cuestin elimine todos sus datos de sus tablas internas.
Para ello se disponen de dos funciones ms: pthread_join() y pthread_detach()

30

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

Sintaxis:
int pthread_join(pthread_t *hilo, void **thread_return);

Esta funcin suspende el thread llamante hasta que no termine su ejecucin el


thread indicado por el parmetro *hilo. Adems, una vez que ste ltimo termina, pone
en **thread_return el resultado devuelto por el thread que se estaba ejecutando. Este
ltimo parmetro puede ser NULL en cuyo caso le estaremos indicando a la librera que
no nos interesa el resultado.
La funcin devuelve cero en caso de xito o un valor diferente de cero en caso
de producirse un error.
Sintaxis:
int pthread_detach(pthread_t *hilo)

Esta funcin indica a la librera que no queremos que nos guarde el resultado de
la ejecucin del thread indicado por el parmetro *hilo. Por defecto la librera guarda el
resultado de la ejecucin de todos los threads hasta que hacemos un pthread_join() para
recoger el resultado. As que si no nos interesa el resultado de los threads, tenemos que
indicarlo con esta funcin. Una vez que el hilo haya terminado, la librera eliminar los
datos del thread de sus tablas internas y se tendr ms espacio disponible para crear
otros hilos.
La funcin devuelve cero en caso de xito o un valor diferente de cero en caso
contrario.
Finalmente, para devolver valores cuando un thread finaliza, se utiliza la funcin
pthread_exit():
Sintaxis:
void pthread_exit(void *thread_return);

Esta funcin termina la ejecucin del thread que la llama. El valor del parmetro
*thread_return queda disponible para pthread_join() si sta tuvo xito. El parmetro
de sta funcin es un apuntador genrico a los datos que se quieren devolver como
resultado. Estos datos sern recogidos ms tarde cuando se haga un pthread_join() a
travs del identificador de nuestro hilo. La funcin no devuelve ningn valor.
PROGRAMA 1: El siguiente programa crear MAX_THREADS threads que
ejecutarn la funcin funcion_thr(). Esta funcin sacar un mensaje por pantalla del
tipo: Hola, soy el thread nmero y, donde y ser un nmero diferente para cada hilo.
Para pasar esos parmetros a la funcin, utilizaremos un struct de C donde se
introducir la cadena que debe imprimir cada hilo ms su identificador (la cadena la
podramos haber metido directamente dentro de la funcin, pero as veremos como se
pasa ms de un parmetro al hilo).

31

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

Una vez terminada su ejecucin, el thread devolver como resultado su


identificados (codificado en un entero), que ser impreso en pantalla por el thread padre
que esperar a que todos los threads terminen:
/* Creamos MAX_THREAD threads que sacan por pantalla una cadena y su
identificador. Una vez que terminan su ejecucion devuelven como
resultado su identificador */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define MAX_THREADS

10

pthread_t tabla_thr[MAX_THREADS]; //tabla con los identificadores de


// los threads
typedef struct
{
int id;
char *cadena;
} thr_param_t;

// tipo de datos y tabla con los parametros

thr_param_t param[MAX_THREADS];
//tenemos que crear una tabla para los parametros porque los pasamos
//por referencia. Asi, si solo tuvieramos una variable para los
//parmetros al modificar esta modificariamos todas las que habiamos
//pasado anteriormente porque los threads no se quedan con el valor
//sino con la direccion
// Esta es la funcion que ejecutan los threads
void * funcion_thr(void *p)
{
thr_param_t *datos;
datos= (thr_param_t *)p;
printf("%s %d\n", datos->cadena, datos->id);
pthread_exit(&(datos->id)); //una vez terminamos, devolvemos el
//valor
}
int main(void)
{
int i, *res;
void * r;
printf("Creando threads...\n");
for (i=0; i<MAX_THREADS; i++)
{
param[i].cadena="Hola, soy el thread";
param[i].id= i;
pthread_create(&tabla_thr[i],NULL,funcion_thr,(void*)&param[i]);
}
printf("Threads creados. Esperando que terminen...\n");
for (i=0; i<MAX_THREADS; i++)
{
pthread_join(tabla_thr[i],&r);

32

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

res=(int *)r;
printf("El thread %d devolvio el valor %d\n", i, *res);
}
printf("Todos los threads finalizados. Adios!\n");
system("PAUSE");
return 0;
}

Una posible salida:

Otra posible salida:

Este primer programa ilustra el esquema bsico que siguen todas las aplicaciones
que lanzan threads para realizar un clculo y esperar su resultado:

33

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

1. Crear el (los) thread(s)


2. Esperar a que terminen
3. Recoger y procesar el (los) resultado(s)
A esto se le llama PARALELISMO ESTRUCTURADO.
PROGRAMA 2. Realizaremos la aplicacin ms tpica de todo lenguaje de
programacin, el programa HOLA MUNDO en su versin multi-hilo. La aplicacin
imprimir el mensaje HOLA MUNDO en la salida estndar (stdout). El cdigo para la
versin secuencial (no multi-hilo) de Hola Mundo es:
#include <stdio.h>
#include <stdlib.h>
void imprimir_mensaje( void *puntero );
int main() {
char *mensaje1 = "Hola";
char *mensaje2 = "Mundo";
imprimir_mensaje((void *)mensaje1);
imprimir_mensaje((void *)mensaje2);
printf("\n");
system("PAUSE");
exit(0);
return 1;
}
void imprimir_mensaje( void *puntero )
{
char *mensaje;
mensaje = (char *) puntero;
printf("%s ", mensaje);
}

Para la versin multihilo necesitamos dos variables de hilo y una funcin de


comienzo para los nuevos hilos que ser llamada cuando comiencen su ejecucin.
Tambin necesitamos alguna forma de especificar que cada hilo debe imprimir un
mensaje diferente. Una aproximacin es dividir el trabajo en cadenas de caracteres
distintas y proporcionar a cada hilo una cadena diferente como parmetro. Examinemos
el programa 3:
PROGRAMA 3. El programa crea el primer hilo llamando a pthread_create( ) y
pasando Hola como argumento inicial. El segundo hilo es creado con Mundo
como argumento. Cuando el primer hilo inicia la ejecucin, comienza en la funcin
imprimir_mensaje( ) con el argumento Hola. Imprime Hola y finaliza la funcin.
Un hilo termina su ejecucin cuando finaliza su funcin inicial. As pues, el primer hilo
termina despus de imprimir el mensaje Hola. Cuando el 2do. Hilo se ejecuta,
imprime Mundo y al igual que el anterior hilo, finaliza.

34

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void * imprimir_mensaje( void *puntero );
int main()
{
pthread_t hilo1, hilo2;
char *mensaje1 = "Hola";
char *mensaje2 = "Mundo";
pthread_create(&hilo1,NULL,imprimir_mensaje,(void*)
mensaje1);
pthread_create(&hilo2,NULL,imprimir_mensaje,(void*)
mensaje2);
printf("\n\n");
system("PAUSE");
exit(0);
return 1;
}
void * imprimir_mensaje( void *puntero )
{
char *mensaje;
mensaje = (char *) puntero;
printf("%s ", mensaje);
return puntero;
}

Aunque este programa parece correcto, posee errores que debemos considerar
como defectos considerables:
1. El defecto ms importante es que los hilos pueden ejecutarse concurrentemente. No
existe entonces garanta de que el primer hilo ejecute la funcin printf() antes que el
segundo hilo. Por tanto, podramos ver el mensaje Mundo Hola en vez de Hola
Mundo.
2. Por otro lado existe una llamada a exit( ) realizada por el hilo padre en la funcin
principal main( ). Si el hilo padre ejecuta la llamada a exit( ) antes de que alguno de
los hilos ejecute printf( ), la salida no se generar completamente. Esto es porque la
funcin exit( ) finaliza el proceso (libera la tarea) y termina todos los hilos.
Cualquier hilo padre o hijo que realice la llamada a exit( ) puede terminar con el
resto de los hilos del proceso. Los hilos que deseen finalizar explcitamente, deben
utilizar la funcin pthread_exit( ).
As nuestro programa Hola Mundo tiene 2 condiciones de ejecucin (race
conditions) o condiciones problemticas:

La posibilidad de ejecucin de la llamada a exit( )


La posibilidad de qu hilo ejecutar la funcin printf( ) primero.

35

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

Una forma de arreglar estas dos condiciones de manera artesanal es la siguiente,


mostrada en el programa 4.
PROGRAMA 4. Como queremos que cada hilo finalice antes que el hilo padre,
podemos insertar un retraso en el hilo padre para dar tiempo a los hilos hijos a que
ejecuten printf(). Para asegurar que el primer hilo ejecute printf() antes que el segundo
hilo, podramos insertar un retraso antes de crear el segundo hilo. El cdigo resultante es
entonces:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void * imprimir_mensaje( void *puntero );
int main()
{
pthread_t hilo1, hilo2;
char *mensaje1 = "Hola";
char *mensaje2 = "Mundo";
pthread_create(&hilo1,NULL,imprimir_mensaje,(void*)mensaje1);
sleep(100);
pthread_create(&hilo2,NULL,imprimir_mensaje,(void*)mensaje2);
sleep(100);
system("PAUSE");
exit(0);
return 1;
}
void * imprimir_mensaje( void *puntero )
{
char *mensaje;
mensaje = (char *) puntero;
printf("%s ", mensaje);
pthread_exit(0);
}

Este cdigo parece funcionar bien, sin embargo no es seguro. Nunca es seguro
confiar en los retrasos de tiempo. La condicin problemtica aqu es exactamente la
misma que se tiene con una aplicacin distribuida y un recurso compartido. El recurso
es la salida estndar y los elementos de la computacin distribuida son los tres hilos. El
hilo 1 debe usar la salida estndar antes que el hilo 2 y ambos deben realizar sus
acciones antes de que el hilo padre realice la llamada a exit( ).
Adems de nuestros intentos por sincronizar los hilos mediante retrasos, hemos
cometido otro error. La funcin sleep( ), al igual que la funcin exit( ) es relativa a
procesos. Cuando un hilo ejecuta sleep( ) el proceso entero duerme, es decir, todos los
hilos duermen cuando el proceso duerme. Nos encontramos por tanto en la misma
situacin que tenamos sin las llamadas a sleep() y encima el programa tarda 200
milisegundos ms en su ejecucin.

36

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

PROGRAMA 5. La versin correcta de HOLA MUNDO empleando primitivas de


sincronizacin que evitan las condiciones de ejecucin no deseadas, se presenta en el
siguiente cdigo:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct param
{
char *mensaje;
pthread_t hilo;
} param_t;
void * imprimir_mensaje( void *puntero )
{
param_t *datos;
datos = (param_t *) puntero;
if (datos->hilo != 0)
pthread_join( datos->hilo, NULL );
printf("%s ", datos->mensaje);
pthread_exit( 0 );
}

int main()
{
pthread_t hilo1, hilo2;
param_t mensaje1 = {"Hola", 0};
param_t mensaje2 = {"Mundo", 0};
pthread_create(&hilo1,NULL,imprimir_mensaje,(void*)&mensaje1);
mensaje2.hilo = hilo1;
pthread_create(&hilo2,NULL,imprimir_mensaje,(void*)&mensaje2);
pthread_join( hilo2, NULL );
system("PAUSE");
exit(0);
return 1;
}

En esta versin, el parmetro de la funcin imprimir_mensaje( ) es del tipo


definido por el usuario param_t que es una estructura que nos permite pasar el mensaje
y el identificador de un hilo.
El hilo padre tras crear el hilo 1, obtiene su identificador y lo almacena como
parmetro para el hilo 2. Ambos hilos ejecutan la funcin inicial imprimir_mensaje( ),
pero la ejecucin es distinta para cada hilo.
El primer hilo contendr un 0 (cero) en el campo hilo de la estructura de datos
con lo que no ejecutar la funcin pthread_join( ); ejecutar printf( ) y la funcin

37

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

pthread_exit( ). El segundo hilo en cambio, contendr el identificador del primer hilo en


el campo hilo de la estructura de datos y ejecutar la funcin pthread_join( ), que lo que
hace es esperar a la finalizacin del primer hilo. Despus ejecuta la impresin con
printf( ) y despus realiza la finalizacin con pthread_exit( ). El hilo padre, tras la
creacin de los 2 hilos se sincroniza con la finalizacin del hilo 2 mediante
pthread_join( ).

II.2.4. Problemas de Concurrencia con Pthreads


Cuando decidimos trabajar con programas concurrentes uno de los mayores
problemas que surgen (inherente a la concurrencia) es el acceso a variables y/o
estructuras compartidas o globales, por ejemplo:
Hilo 1: void * fncion_hilo_1(void *arg)
{
int resultado;
.
.
.
if (i == valor_cualquiera)
{
.
.
.
resultado = i * (int) *arg;
.
.
.
}
pthread_exit(&resultado);
}
Hilo 2: void * fncion_hilo_2(void *arg)
{
int otro_resultado;
.
.
.
if (funcion_sobre_arg(*arg) == 0)
{
.
.
.
i =*arg;
.
.
.
}
pthread_exit(&otro_resultado);
}

38

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

ste cdigo que tiene a la variable i como global puede ser problemtico si se
ejecuta en paralelo y se dan ciertas condiciones.
Supongamos que el hilo 1 empieza a ejecutarse antes que el hilo 2 y que
casualmente se produce un cambio de contexto (el S.O. suspende la tarea actual y pasa a
ejecutar la siguiente) justo despus de la lne que dice if (i == valor_cualquiera).
Supondremos adems que la condicin se cumple por lo que la entrada en ese if
se producir. Pero justo en ese momento se realiza el cambio de contexto por el sistema
y pone a ejecutar el hilo 2, que se ejecuta el tiempo suficiente como para ejecutar la
lnea: i =*arg;
Al poco rato, el hilo 2 deja de ejecutarse volviendo a la ejecucin del hilo 1.
Qu valor tiene ahora i ? El que el hilo 1 esta suponiendo que tiene o el que le
a asignado el hilo 2 ?: i ha tomado el valor asignado por el hilo 2, con lo que el
resultado que devolver el hilo 1 despus de sus clculos ser invlido o inesperado.
Estos son los errores ms difciles de detectar, ya que puede que a veces vaya
todo a la perfeccin cuando se ejecuta el cdigo, pero puede ser que en otras ocasiones
dalga todo mal. A esto se le conoce como: Race Conditions (Condiciones de carrera)
porque segn como vaya la cosa puede funcionar o no.

II.2.5. Mecanismos de Pthreads para prevenir los problemas de


concurrencia
La librera de Pthreads ofrece mecanismos bsicos para prevenir los problemas
descritos anteriormente. Estos mecanismos son llamados SEMAFOROS BINARIOS y
se utilizan para implementar las llamadas REGIONES CRITICAS (RC) o ZONAS
DE EXCLUSION MUTUA (ZE).
Regin Crtica (RC): Es una parte de nuestro cdigo susceptible de verse afectada por
cosas como la del ejemplo anterior.
Regla General: Siempre que haya variables o estructuras globales que vayan a ser
accedidas por ms de un thread a la vez, el acceso a stas deber ser considerado una
regin crtica y protoegido con los medios que proporciona la librera de los Pthreads
(que se explican ms adelante).
Pthreads ofrece los semforos binarios llamados tambin Semforos Mutex o
simplemente mutex.
Semforo Binario: Es una estructura de datos que acta como un semforo porque
puede tener dos estados: abierto o cerrado. Cuando el semforo esta abierto al primer
thread que pide un bloqueo se le asigna ese bloqueo y no deja pasar a nadie ms por el
semforo. Mientras que si el semforo esta cerrado, porque algn thread tenga ya el
bloqueo, el thread que lo pidi parar su ejecucin hasta que no sea liberado el
susodicho bloqueo.
Slo puede haber un solo thread poseyendo el bloqueo del semforo, mientras que
puede haber ms de un thread esperando para entrar en la regin crtica, enconlados en

39

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

la cola de espera del semforo. Los threads por tanto se excluyen mutuamente (mutex)
el uno al otro para entrar.
Forma de implementar las RC: Se pide el bloqueo del semforo antes de entrar, ste
es otorgado al primero que llega mientras que los dems se quedan bloqueado,
esperando a que el que entr primero libere el bloqueo o exclusin. Una vez que el que
entr sale de la regin crtica, ste debe notificarlo a la librera de los Pthreads para que
mire si hay algn otro thread esperando a entrar en la cola. Si es as, le da el bloqueo al
primero y deja que siga ejecutndose.

II.2.5.1. Sincronizacin de Hilos


Como ya sabemos, los hilos se crean dentro del espacio de direcciones de un
proceso y comparten recursos como las variables estticas y los descriptores de
archivos. Cuando los hilos utilizan recursos compartidos como estos; deben sincronizar
su interaccin a fin de obtener resultados consistentes. Hay dos tipos distintos de
sincronizacin:

Uso de candados (locking): Se refiere por lo regular a la tenencia a corto plazo de


recursos.
La espera: Se refiere al bloqueo hasta que ocurre cierto suceso. Puede tener
duracin ilimitada.

La librera Pthreads proporciona mutex y variables de condicin para apoyar


estos dos tipos de sincronizacin en programas multihilos. Adems permite el empleo
de semforos en este tipo de programas.

II.2.5.2. Exclusin Mutua o MUTEX


El mutex o candado mutex (mutex lock) es el mecanismo de sincronizacin de
hilos ms sencillo y eficiente. Los programas que emplean mutex preservan regiones
crticas y obtienen acceso exclusivo a los recursos. En otras palabras, con mutex se trata
de indicar que una regin particular de cdigo (RC) slo puede ser ejecutada por un
determinado hilo al mismo tiempo. Primeramente un programa debe declarar una
variable de tipo:
pthread_mutex_t

e inicializarla antes de utilizarla para su sincronizacin. Por lo general, las variables de


mutex son variables estticas accesibles para todos los hilos del proceso. Aunque un
programa puede inicializar un mutex empleando el inicializador esttico:
PTHREAD_MUTEX_INITIALIZER

40

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

es ms comn invocar a la funcin de inicializacin:


int pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutexattr_t *attr);

Esta funcin inicializa un mutex. Hay que llamarla antes de usar cualquiera de
las funciones que trabajan con mutex.
*mutex: es un apuntador a un parmetro del tipo pthread_mutex_t que es el tipo de
datos que utiliza la librera Pthreads para controlar los mutex.
*attr: Es un apuntador a una estructura del tipo pthread_mutexattr_t y sirve para definir
que tipo de mutex queremos: normal, recursivo o errorcheck. Si este valor es NULL
(recomendado), la librera le asignar un valor por defecto.
La funcin devuelve 0 (cero) si se pudo crear el mutex o -1 si hubo algn error.
Ejemplo 1. El siguiente segmento de cdigo inicializa el mutex my_lock con los
atributos por defecto. La variable my_lock debe ser accesible para todos los hilos que la
usan:
#include
#include
#include
#include

<stdio.h>
<pthread.h>
<string.h>
<errno.h>

pthread_mutex_t

my_lock;

if (! Pthread_mutex_init(&my_lock, NULL))
perror(No puede inicializarse my_lock);

El mtodo del inicializador esttico tiene 2 ventajas respecto de


pthread_mutex_init( ), al inicializar candados mutex: suele ser ms eficiente y se
garantiza que se ejecutar una y slo una vez antes de que se inicie la ejecucin de
cualquier hilo.
Ejemplo 2. El segmento fragmento de cdigo inicializa el mutex my_lock con los
atributos por omisin empleando el inicializador esttico:
#include <pthread.h>
pthread_mutex_t

my_lock= PTHREAD_MUTEX_INITIALIZER;

Un mutex tiene dos estados: bloqueado y desbloqueado. Inicialmente esta


desbloqueado. La funcin pthread_mutex_lock( ) bloquea el mutex y contina con la
ejecucin:
SINTAXIS:
int pthread_mutex_lock(pthread_mutex_t *mutex);

41

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

La function pide el bloqueo para entrar en una regin crtica (RC). Si se quiere
implementar una RC todos los hilos tendrn que pedir el bloqueo sobre el mismo
candado. Esta funcin devuelve 0 (cero) si no hubo error, o un valor diferente de cero si
lo hubo.
Al finalizar el bloqueo, se debe desbloquear el mutex mediante la funcin
pthread_mutex_unlock( ).
SINTAXIS:
int pthread_mutex_unlock(pthread_mutex_t *mutex);

sta es una funcin contraria a la anterior y libera el bloqueo que se tuviera


sobre un candado o mutex. La funcin retorna 0 (cero) como resultado si no hubo error,
o un valor diferente de cero si lo hubo.
SINTAXIS:
int pthread_mutex_destroy(pthread_mutex_t *mutex);

Esta funcin le dice a la librera que el mutex que le estamos indicando no lo


vamos a usar ms y que podemos liberar toda la memoria ocupada por ese mutex en sus
estructuras internas. La funcin devuelve 0 (cero) si no hubo error o algo diferente de
cero si lo hubo.
En general, un hilo puede utilizar un pthread_mutex_lock( ) (candado mutex)
para proteger sus regiones crticas (RC). A fin de preservar la semntica de la regin
crtica, el mutex debe ser liberado por el mismo hilo que lo adquiri, es decir, no
debemos hacer que un hilo adquiera un candado y otro lo libere. Una vez que un hilo
adquiera un mutex deber conservarlo durante un tiempo corto, en otras palabras,
cuanto ms pequeas son las regiones crticas ms concurrentes son los programas ya
que tendrn que esperar menos tiempo en el caso de que hayan bloqueos.
Los hilos que estn esperando sucesos de duracin impredecible deberan utilizar
otros mecanismos de sincronizacin como los semforos y las variables de condicin.
Ejemplo 3. El siguiente cdigo utiliza un mutex para proteger una regin crtica (RC):
#include <pthread.h>
pthread_mutex_t my_lock= PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&my_lock);
/*
REGION CRITICA
*/
pthread_mutex_unlock(&my_lock);

Ejemplo 4. Si reescribimos el pseudocdigo del ejemplo anterior al ejemplo 1 con lo


que hemos visto hasta ahora, la modificacin sera la siguiente:

42

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

Variables globales: pthread_mutex_t mutex_acceso, int i;


Hilo 1: void * fncion_hilo_1(void *arg)
{
int resultado;
.
.
.
pthread_mutex_lock(&mutex_acceso);
if (i == valor_cualquiera)
{
.
.
.
resultado = i * (int) *arg;
.
.
.
}
pthread_mutex_unlock(&mutex_acceso);
pthread_exit(&resultado);
}
Hilo 2: void * fncion_hilo_2(void *arg)
{
int otro_resultado;
.
.
.
if (funcion_sobre_arg(*arg) == 0)
{
.
.
.
pthread_mutex_lock(&mutex_acceso);
i =*arg;
pthread_mutex_unlock(&mutex_acceso);
.
.
.
}
pthread_exit(&otro_resultado);
}
int main( void){
.
.
.
pthread_mutex_init(&mutex_acceso, NULL);
.
.
.
}

43

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

Como puede observarse lo nico que hay que hacer es: inicializar el candado o
mutex, pedir el bloqueo antes de la RC y liberarlo despus de salir de la RC.
Ejemplo 5. muestra un fragmento de cdigo donde el mutex m esta asociado con la
variable global cabeza. Las funciones pthread_mutex_lock( ) y pthread_mutex_unlock(
) proporcionan exclusin mutua, lo que permite aadir sin problemas de interferencias,
la variable local nuevo_elemento a la lista enlazada cuya cabeza es cabeza.
typedef struct lista
{
char car;
lista_t *next;
} lista_t;
pthread_mutex_t m;
lista_t *cabeza;
void insertar_elemento(lista_t *nuevoElemento)
{
pthread_mutex_lock(&m)
nuevoElemento next = cabeza;
cabeza= nuevoElemento;
pthread_mutex_unlock(&m);
}

Un mutex normalmente esta relacionado con una variable global o estructura de


datos global, como ocurre en el ejemplo anterior. En este caso como mucho un hilo se
encuentra ejecutando el cdigo de la RC en un instante de tiempo dado.
IMPORTANTE: La adquisicin y liberacin de candados son voluntarias en el sentido
de que slo se logra la exclusin mutua cuando todos los hilos adquieren correctamente
el mutex antes de regresar de sus RC.
No hay nada que impida a un hilo no cooperativo ingresar en su RC sin adquirir un
candado. Una forma de asegurar el acceso exclusivo a objetos es permitir el acceso slo
a travs de funciones bien definidas e incluir las llamadas de adquisicin de candados en
esas funciones. As el mecanismo de candados ser transparente para los hilos
invocadores.
PROGRAMA 6. La figura siguiente muestra una implementacin de una cola como un
buffer circular con 8 ranuras o slots y 3 elementos de datos:
bufin
buout

44

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

El valor bufout indica el nmero de ranura del siguiente elemento de datos que
se va a sacar y el valor bufin indica la siguiente ranura que se llenar. Si los hilos
productores y consumidores no actualizan bufout y bufin de una forma mutuamente
exclusiva, un productor prodra sobrescribir un elemento que no ha sido sacado o un
consumidor podra sacar un elemento que ya se us.
El cdigo siguiente muestra un programa que implementa un buffer circular
como un objeto compartido. El cdigo esta en un archivo aparte para que el programa
slo pueda acceder a buffer a travs de get_item (obtener elemento) y put_item (insertar
elemento).
#include <pthread.h>
#define BUFSIZE 8
static int buffer[BUFSIZE];
static int bufin=0;
static int bufout=0;
static pthread_mutex_t buffer_lock= PTHREAD_MUTEX_INITIALIZER;
/* Obtener el siguiente elemento del buffer y
*itemp */
void get_item(int *itemp)
{
pthread_mutex_lock(&buffer_lock);
*itemp=buffer[bufout];
bufout=(bufout+1) % BUFSIZE;
// printf("Tamano de bufout: %d\n",bufin);
pthread_mutex_unlock(&buffer_lock);
return;
}

colocarlo

/*colocar un elemento en el buffer en la posicin


actualizar bufin */
void put_item(int item)
{
pthread_mutex_lock(&buffer_lock);
buffer[bufin]=item;
bufin=(bufin+1) % BUFSIZE;
// printf("Tamano de bufin: %d\n",bufin);
pthread_mutex_unlock(&buffer_lock);
return;
}

bufin

en

El programa siguiente es un ejemplo sencillo en el que un hilo productor escribe


los cuadrados de los enteros del 1 al 100 en el buffer circular y un hilo consumidor saca
los valores y los suma.
Aunque el buffer est protegido con candados mutex, la sincronizacin
productor-consumidor no es correcta. El consumidor puede sacar elementos de ranuras
vacas y el productor puede sobrescribir ranuras llenas:

45

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "buffer_circ.h"
#define SUMSIZE 100
int sum=0;
void put_item(int);
void get_item(int *);
void producer(void *arg1)
{
int i;
for(i=1;i<=SUMSIZE;i++)
{
printf("Se ejecuta PRODUCER: %d\n",i);
put_item(i*i);
}
}
void consumer(void *arg2)
{
int i, myitem;
for(i=1;i<=SUMSIZE;i++)
{
printf("Se ejecuta CONSUMER: %d\n",i);
get_item(&myitem);
sum+=myitem;
}
}
int main(void)
{
pthread_t prodtid;
pthread_t constid;
int i, total;
total=0;
for(i=1;i<=SUMSIZE;i++)
total+=i*i;
printf("La suma actual debe ser %d\n",total);
/* CREAR HILOS */

if(pthread_create(&constid,NULL,(void*(*)(void*))&consumer,NULL)
)
perror("No se pudo crear el hilo consumer...");

46

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

else
if(pthread_create(&prodtid,NULL,(void*(*)(void*))&producer,NULL)
)
perror("No se pudo crear el hilo producer...");
/* ESPERAR A QUE LOS HILOS TERMINEN */
pthread_join(prodtid,NULL);
pthread_join(constid,NULL);
printf("Los threads produjeron la suma: %d\n",sum);
system("PAUSE");
exit(0);
return 1;
}

Un posible resultado de la ejecucin de este programa sera:


La suma actual debe ser 338350
Se ejecuta CONSUMER: 1
Se ejecuta CONSUMER: 2
Se ejecuta CONSUMER: 3
Se ejecuta CONSUMER: 4
Se ejecuta CONSUMER: 5
Se ejecuta CONSUMER: 6
Se ejecuta CONSUMER: 7
Se ejecuta CONSUMER: 8
Se ejecuta CONSUMER: 9
Se ejecuta CONSUMER: 10
Se ejecuta CONSUMER: 11
Se ejecuta CONSUMER: 12
Se ejecuta CONSUMER: 13
Se ejecuta CONSUMER: 14
Se ejecuta CONSUMER: 15
Se ejecuta CONSUMER: 16
Se ejecuta PRODUCER: 1
Se ejecuta PRODUCER: 2
Se ejecuta PRODUCER: 3
Se ejecuta PRODUCER: 4
Se ejecuta PRODUCER: 5
Se ejecuta PRODUCER: 6
Se ejecuta PRODUCER: 7
Se ejecuta PRODUCER: 8
.
.
.
Se ejecuta PRODUCER: 94
Se ejecuta PRODUCER: 95
Se ejecuta PRODUCER: 96
Se ejecuta PRODUCER: 97
Se ejecuta PRODUCER: 98
Se ejecuta PRODUCER: 99
Se ejecuta PRODUCER: 100
Se ejecuta CONSUMER: 85
Se ejecuta CONSUMER: 86
Se ejecuta CONSUMER: 87
Se ejecuta CONSUMER: 88
Se ejecuta CONSUMER: 89
Se ejecuta CONSUMER: 90
Se ejecuta CONSUMER: 91

47

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

Se ejecuta CONSUMER: 92
Se ejecuta CONSUMER: 93
Se ejecuta CONSUMER: 94
Se ejecuta CONSUMER: 95
Se ejecuta CONSUMER: 96
Se ejecuta CONSUMER: 97
Se ejecuta CONSUMER: 98
Se ejecuta CONSUMER: 99
Se ejecuta CONSUMER: 100
Los threads produjeron la suma: 315430
Presione una tecla para continuar . . .

PROGRAMA 7. Si se incluye la instruccin sched_yield en el programa anterior, el


productor y el consumidor operan en alternancia estricta siempre que solo un hilo se
ejecute a la vez. Para que el programa funcione a la perfeccin, tiene que iniciar el hilo
productor y el hilo consumidor debe estar en activo en el momento en que el productor
ceda su lugar por primera vez.
#include
#include
#include
#include
#include
#include

<pthread.h>
<stdio.h>
<stdlib.h>
<string.h>
<errno.h>
"buffer_circ.h"

#define SUMSIZE 100


int sum=0;
void put_item(int);
void get_item(int *);

48

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

void producer(void *arg1)


{
int i;
for(i=1;i<=SUMSIZE;i++)
{
printf("Se ejecuta PRODUCER...%d\n",i);
put_item(i*i);
// pthread_yield();
sched_yield(); //Obliga al hilo productor ceder su lugar
en
// cada iteracin
}
}
void consumer(void *arg2)
{
int i, myitem;
for(i=1;i<=SUMSIZE;i++)
{
printf("Se ejecuta CONSUMER...%d\n",i);
get_item(&myitem);
sum+=myitem;
// pthread_yield();
sched_yield(); // Obliga al hilo consumidor ceder su
lugar
// en cada iteracin
}
}
int main(void)
{
pthread_t prodtid;
pthread_t constid;
int i, total;
total=0;
for(i=1;i<=SUMSIZE;i++)
total+=i*i;
printf("La suma actual debe ser %d\n",total);
/* CREAR HILOS */

if(pthread_create(&constid,NULL,(void*(*)(void*))&producer,NULL)
)
perror("No se pudo crear el hilo consumer...");
else
if(pthread_create(&prodtid,NULL,(void*(*)(void*))&consumer,NULL)
)
perror("No se pudo crear el hilo producer...");

49

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

/* ESPERAR A QUE LOS HILOS TERMINEN */


pthread_join(prodtid,NULL);
pthread_join(constid,NULL);
printf("Los threads produjeron la suma: %d\n",sum);
system("PAUSE");
exit(0);
return 1;
}

La instruccin pthread_yield( ) para el POSIX.1c o sched_yield( ) para el POSIX.1b


cede su lugar a un hilo en alternancia estricta. La salida del programa es como la que se
muestra a continuacin:

La alternancia estricta no resuelve el problema cuando hay nmeros arbitrarios


de productores y consumidores. El comportamiento del programa depende del nmero

50

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

exacto de hilos que pueden ejecutarse de forma concurrente y del orden en que inician
su ejecucin.
Un programa multihilo debe funcionar correctamente, sea cual fuere el orden de
ejecucin y el nivel de paralelismo. Los elementos y ranuras del problema de
productores y consumidores deben sincronizarse de modo que el programa sea
independiente del orden de ejecucin de los hilos. Un mtodo tradicional de
sincronizacin es aquel que emplea semforos.

II.2.5.3. Mas Problemas???


El problema de los DEADLOCKS (o ABRAZOS MORTALES): Los DEADLOCKS se
producen cuando un hilo se bloquea esperando un recurso que tiene bloqueado otro hilo
que esta esperando un recurso. Si el recurso para el segundo thread no llega nunca, no se
desbloquear con lo cual tampoco se desbloquear el primer thread. El resultado ser
nuestro programa bloqueado. Algunas posibles soluciones son:
a) Usar candados recursivos (mutex recursivos)
b) Probar antes de entrar con el uso de int pthread_mutex_trylock( ).

II.2.5.4. Las variables de Condicin


Se puede ver a un mutex como un tipo simple de planificacin de recursos. El
recurso a planificar es la memoria compartida accedida dentro de las funciones de
bloqueo y desbloqueo del mutex y la poltica de planificacin es un nico hilo al mismo
tiempo.
Sin embargo a veces el programador necesita expresar polticas de planificacin
ms complicadas. Esto requiere el uso de un mecanismo que permite a un hilo
suspenderse hasta que suceda algn determinado evento. Este mecanismo de espera por
un evento se consigue mediante una variable de condicin. Una variable de condicin
esta siempre asociada con un mutex particular y con los datos protegidos por el mutex.
Suponga que un hilo necesita esperar hasta que se cumpla cierto predicado en el
que interviene un conjunto de variables compartidas (por ejemplo, que dos de esas
variables sean iguales). Estas variables compartidas se protegern con un candado
mutex, pues cualquier segmento de cdigo que las utilice forma parte de una RC. Una
variable de condicin adicional ofrece un mecanismo para que los hilos esperen el
cumplimiento de predicados en los que intervienen esas variables. Cada vez que un hilo
modifica una de estas variables compartidas, sealiza mediante la variable de condicin
que se ha realizado un cambio. Esta seal activa un hilo en espera, que entonces
determina si ahora su predicado se cumple.
Cuando se enva una seal a un hilo en espera, ste debe cerrar el mutex antes de
probar su predicado. Si el predicado es falso, el hilo deber liberar el candado y
bloquearse de nuevo. El mutex debe liberarse antes de que el hilo se bloquee para que
otro hilo pueda tener acceso al mutex y modificar las variables protegidas. La liberacin
del mutex y el bloqueo deben ser atmicos para evitar que otro hilo modifique las
variables entre estas dos operaciones. La seal slo indica que las variables pueden

51

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

haber cambiado, no que se ha cumplido el predicado y el hilo bloqueado debe entonces


volver a probar el predicado cada vez que reciba una seal.
Hay que seguir los pasos siguientes al utilizar una variable de condicin para
sincronizar con base en un predicado arbitrario:
1.
2.
3.
4.

Adquiera el mutex
Pruebe el predicado
Si el predicado se cumple, realice el trabajo y libere el mutex
Si el predicado no se cumple, llame a cond_wait y pase al paso 2 cuando
retorne.

As, una variable de condicin se inicializa ya sea empleando un inicializador esttico:


pthread_cond_t v = PTHREAD_COND_INITIALIZER;

o llamando a la funcin pthread_cond_init( ):


int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t
*attr);

Si *attr es NULL (recomendado) pthread_cond_init utiliza los atributos de variable de


condicin por omisin.
La funcin pthread_cond_wait( ) desbloquea el mutex y suspende el hilo (lo
encola a la espera de la variable de condicin) de forma atmica, en una sola accin.
int pthread_cond_wait(pthread_cond_t
*mutex);
Un hilo prueba entonces un predicado*cond,
y llamapthread_mutex_t
a pthread_cond_wait(
) si el

predicado es falso. Cuando otro hilo modifica variables que podran hacer que se
cumpliera el predicado, activa al hilo bloqueado ejecutando pthread_cond_signal( ).
int pthread_cond_signal(pthread_cond_t *cond);

La funcin pthread_cond_signal( ) no hace nada a menos que exista un hilo


bloqueado en la variable de condicin, en cuyo caso despierta uno de los hilos
bloqueados a la espera.
La funcin pthread_cond_broadcast( ) es similar a la anterior excepto que
despierta a todos los hilos suspendidos a la espera de la variable de condicin.
int pthread_cond_broadcast(pthread_cond_t *cond);

A fin de garantizar que la prueba del predicado y la espera sean atmicas, el hilo
invocador debe obtener un mutex antes de probar el predicado. La implementacin
garantiza que si el hilo se bloquea en una variable de condicin, pthread_cond_wait( )
libera automticamente el mutex y se bloquear. Otro hilo no podr emitir una seal
antes de que este hilo se bloquee.

52

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

Cuando un hilo es despertado despus de ser suspendido en la funcin


pthread_cond_wait( ) , vuelve a intentar adquirir el mutex y despus contina su
ejecucin. Si el mutex no esta disponible, el hilo se suspende a la espera de que el
mutex este disponible. El mutex asociado con una variable de condicin protege los
datos compartidos. Si un hilo quiere un recurso bloquea el mutex y examina los datos
compartidos. Si el recurso esta disponible el hilo contina, sino, el hilo libera el mutex y
se suspende llamando a pthread_cond_wait( ). Despus, cuando algn hilo termina con
el recurso, despierta al primer hilo mediante pthread_cond_signal( ) o
pthread_cond_broadcast( ).
El siguiente fragmento de cdigo permite que un hilo se suspenda hasta que una
lista enlazada, cuya cabeza es cabeza, no es vaca y entonces elimina el elemento de
la cabeza de la lista.
Ejemplo 6. pthread_mutex_t m;
Pthread_cond_t condNoVacio;
Lista_t *cabeza;
Lista_t * extraer_elemento(void)
{
pthread_mutex_lock(&m);
while(cabeza==NULL)/*condicin de espera */
pthread_cond_wait(&condNoVacio, &m);
elemento=cabeza;
cabeza=cabeza next;
pthread_mutex_unlock(&m);
return elemento;
}

y el siguiente fragmento puede ser empleado para un hilo que aade un elemento a la
cabeza de la lista:
void insertar_elemento(lista_t *nuevoelemento)
{
pthread_mutex_lock(&m);
nuevoelemento next = cabeza;
cabeza = nuevoelemento;
pthread_cond_signal(&condNoVacio);
pthread_mutex_unlock(&m);
}

En el ejemplo, dentro de la funcin extraer_elemento( ) el hilo debe adquirir el


mutex m especificado en la funcin pthread_mutex_lock( ) antes de invocar a
pthread_cond_wait( ). Si la condicin (cabeza == NULL ) es verdadera, el hilo
ejecutar pthread_cond_wait( ), liberar el mutex m y se bloquear en la variable de
condicin condNoVacio.
Cuando un hilo ejecuta la funcin pthread_cond_wait( ) en el ejemplo anterior,
esta en posesin del mutex m. El hilo se bloquea atmicamente y libera el mutex ,
permitiendo que otro hilo adquiera el mutex y modifique las variables del predicado.
Cuando un hilo regresa con xito de una funcin pthread_cond_wait( ), ha
adquirido el mutex y puede volver a probar el predicado sin volver a adquirir
explcitamente el mutex.
La funcin pthread_cond_wait( ) activa un hilo que esta esperando una variable
de condicin. Si hay ms hilos esperando, se escoge uno siguiendo un criterio

53

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

congruente con el algoritmo de programacin utilizado. La funcin


pthread_cond_broadcast( ) activa todos los hilos que esperan una variable de condicin.
Estos hilos activados entran en contencin por el candado mutex antes de regresar de
pthread_cond_wait( ).
He aqu algunas reglas para el uso de las variables de condicin:

Adquiera el mutex antes de probar el predicado


Vuelva a probar el predicado despus de regresar de un pthread_cond_wait( )
pues el retorno podra deberse a algn suceso no relacionado o bien a un
pthread_cond_signal( ) que no implica que el predicado ya se cumpla.
Adquiera el mutex antes de modificar alguna de las variables que aparecen en el
predicado
Adquiera el mutex antes de llamar al as funciones pthread_cond_signal( ) o
pthread_cond_broadcast( ).
Retenga el mutex durante un periodo corto, por lo regular slo mientras prueba
el predicado. Libere el mutex lo antes posible, ya sea de forma explcita con
pthread_mutex_unlock( ) o de forma implcita con pthread_cond_wait( ).

PROGRAMA 8. Una solucin con variables de condicin al Problema del buffer


acotado controlado por el productor. El productor termina despus de producir una
cantidad fija de elementos. El consumidor contina hasta que procesa todos los
elementos y detecta que el productor ya termin.
#include
#include
#include
#include
#include
#include

<pthread.h>
<stdio.h>
<stdlib.h>
<string.h>
<errno.h>
"buffer_circ.h"

#define SUMSIZE 100


#define BUFSIZE 8
int sum=0;
pthread_cond_t slots= PTHREAD_COND_INITIALIZER;
pthread_cond_t items= PTHREAD_COND_INITIALIZER;
pthread_mutex_t slot_lock= PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t item_lock= PTHREAD_MUTEX_INITIALIZER;
int nslots=BUFSIZE;
int producer_done=0;
int nitems=0;
void get_item(int *itemp);
void put_item(int item);
void producer(void *arg1)
{
int i;

54

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

for(i=1;i<=SUMSIZE;i++)
{
pthread_mutex_lock(&slot_lock); //Adquirir derecho a una
//ranura...
while (nslots<=0)
pthread_cond_wait(&slots,&slot_lock);
nslots--;
pthread_mutex_unlock(&slot_lock);
printf("Se ejecuta PRODUCER...%d\n",i);
put_item(i*i);
pthread_mutex_lock(&item_lock);

//renunciar derecho a un
//elemento...

nitems++;
pthread_cond_signal(&items);
pthread_mutex_unlock(&item_lock);
}
pthread_mutex_lock(&item_lock);
producer_done=1;
pthread_cond_broadcast(&items);
pthread_mutex_unlock(&item_lock);
}
void consumer(void *arg2)
{
int i=0, myitem;
for( ; ; )
{
pthread_mutex_lock(&item_lock); /* Adquirir derecho a un
elemento */
while((nitems<=0)&& !producer_done)
pthread_cond_wait(&items,&item_lock);
if((nitems<=0) && producer_done)
{
pthread_mutex_unlock(&item_lock);
break;
}
nitems--;
pthread_mutex_unlock(&item_lock);
i++;
printf("Se ejecuta CONSUMER...%d\n",i);
get_item(&myitem);
sum+=myitem;
pthread_mutex_lock(&slot_lock); /*renunciar derecho a una
ranura */
nslots++;
pthread_cond_signal(&slots);
pthread_mutex_unlock(&slot_lock);
}
}

55

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

int main(void)
{
pthread_t prodtid;
pthread_t constid;
int i, total;
total=0;
for(i=1;i<=SUMSIZE;i++)
total+=i*i;
printf("La suma actual debe ser %d\n",total);
/* CREAR HILOS */
pthread_create(&constid,NULL,(void*(*)(void*))&consumer,NULL);
pthread_create(&prodtid,NULL,(void*(*)(void*))&producer,NULL);
/* ESPERAR A QUE LOS HILOS TERMINEN */
pthread_join(prodtid,NULL);
pthread_join(constid,NULL);
printf("Los threads produjeron la suma: %d\n",sum);
system("PAUSE");
exit(0);
return 1;
}

El productor de este programa se bloquea hasta que se cumple el predicado


nslots>0. Su prueba puede escribirse: while(!(nslots>0)), o bien ((nitems>0)
producer_done), es decir, el consumidor debe actuar si hay un elemento disponible o
si el productor ya termin. Por tanto la prueba aqu es si el while ((nitems<=0)&&
!(producer_done)). El consumidor terminar solo si el productor ya acab y no quedan
ms elementos. Un posible resultado de ejecucin sera el siguiente:

56

Programacin con POSIX Threads

Dr. Mario Rossainz Lpez

57

También podría gustarte