Documentos de Académico
Documentos de Profesional
Documentos de Cultura
GUÍA DE LABORATORIO
INFORMACIÓN BÁSICA
OBJETIVOS/TEMAS Y COMPETENCIAS
OBJETIVOS:
- Implementar cerraduras mutex en pthreads
- Aplicar soluciones al problema de exclusión mutua
TEMAS:
• Estructura de la función phtread_mutex_lock
• Estructura de la función phtread_mutex_unlock
• Exclusión mutua
CONTENIDO DE LA GUÍA
I. MARCO CONCEPTUAL
Problemas de concurrencia con Pthreads
Cuando decidimos trabajar con programas concurrentes uno de los mayores problemas con los que nos
podremos encontrar, y que es inherente a la concurrencia, es el acceso a variables y/o estructuras
compartidas o globales. Esto se entenderá mejor con un ejemplo:
Hilo 1
void *funcion_hilo_1(void *arg)
{
int resultado;
...
if (i == valor_cualquiera) {
...
resultado = i * (int)*arg;
...
}
pthread_exit(&resultado);
}
Hilo 2
void *funcion_hilo_2(void *arg)
{
int otro_resultado;
...
if (funcion_sobre_arg(*arg) == 0) {
...
i = *arg;
...
}
pthread_exit(&otro_resultado);
}
Este código, que tiene la variable 'i' como global, aparentemente es inofensivo, pero nos puede traer muchos
problemas si se ejecuta en paralelo y se dan ciertas condiciones.
Supongamos que el hilo 1 se empieza a ejecutar antes que el hilo 2, y que casualmente se produce un cambio
de contexto (el sistema operativo suspende la tarea actual y pasa a ejecutar la siguiente) justo después de la
línea que dice if (i==valor_cualquiera). La entrada en ese if se producirá si se cumple la condición, que
suponemos que sí.
Pero justo en ese momento el sistema hace un cambio de contexto y pone a ejecutar al hilo2, que se ejecuta
el tiempo suficiente como para ejecutar la línea i = *arg. Al poco rato hilo 2 deja de ejecutarse y vuelve a
UNIVERSIDAD NACIONAL DE SAN AGUSTIN
FACULTAD DE INGENIERÍA DE PRODUCCIÓN Y SERVICIOS
ESCUELA PROFESIONAL DE INGENIERÍA DE SISTEMA
ejecutarse el hilo 1, pero, ¿qué valor tiene ahora i? ¿El que el hilo 1 está "suponiendo" que tiene (o sea, el
mismo que comprobó al entrar en el if) o el que le ha asignado el hilo 2? La respuesta es fácil... ;-) i ha
tomado el valor que le asignó hilo 2, con lo que el resultado que devolverá el hilo 1 después de sus cálculos
será totalmente inválido e inesperado.
Claro que todo esto puede que no pasará si el sistema tuviera muy pocos procesos en ese momento (con lo
cual cada proceso se ejecutaría por más rato) y si el código del hilo 1 fuera lo suficientemente corto como
para no sufrir ningún cambio de contexto en medio de su ejecución... Pero NUNCA deberemos hacer
suposiciones de éstas, porque no sabremos dónde se van a ejecutar nuestros programas y siempre más vale
prevenir.
El problema que tienen estos bugs es que son los más difíciles de detectar en el caso que no nos fijáramos en
que podría pasar una cosa de estas el día que escribimos el código. Puede que a veces vaya todo a la
perfección y que otras salga todo mal... A esto se le conoce por Race Conditions (Condiciones de Carrera)
porque según como vaya la cosa puede funcionar o no.
La biblioteca de hilos en Linux incorpora Locks o Mutexes, para proteger regiones críticas del código, y las
llamadas básicas son las siguientes: pthread_mutex_init, pthread_mutex_lock , pthread_mutex_unlock y
pthread_mutex_destroy .
Notamos que tenemos llamadas para destruir el objeto Mutex a fin de liberar los recursos que este pueda
consumir.
En este caso nuestro ejemplo sincroniza los accesos a un buffer compartido por parte de dos componentes,
donde una establece un valor i en todo el buffer y la otra un valor j distinto. La idea es que queremos que se
mantenga el invariante que todo hilo externo siempre vea el buffer con un contenido uniforme de valores, ya
sea i o j.
Claramente si no protegemos con regiones críticas, es posible que a la mitad de una actualizacion, que no es
atómica, se produzca un cambio de contexto, ya sea para inspeccionar los valores o para establecerlos en
otro valor.
Lo que sigue es el programa setBuffer que implementa esta idea y protege a las secciones críticas utilizando
Locks.
Las funciones que ofrece Pthreads para llevar esto a cabo son:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr)
• Esta función inicializa un mutex. Hay que llamarla antes de usar cualquiera de las funciones que trabajan
con mutex.
• mutex: Es un puntero a un parámetro del tipo pthread_mutex_t, que es el tipo de datos que usa la librería
Pthreads para controlar los mutex.
• attr: Es un puntero a una estructura del tipo pthread_mutexattr_t, y sirve para definir qué tipo de mutex
queremos: normal, recursivo o errorcheck (esto se verá más adelante)
Si este valor es NULL (recomendado), la librería le asignará un valor por defecto.
• La función devuelve 0 si se pudo crear el mutex o -1 si hubo algún error.
int pthread_mutex_lock(pthread_mutex_t *mutex)
• Esta función pide el bloqueo para entrar en una RC. Si queremos implementar una RC, todos los thread
tendrán que pedir el bloqueo sobre el mismo semáforo.
• mutex: Es un puntero al mutex sobre el cual queremos pedir el bloqueo o sobre el que nos bloquearemos
en caso de que ya haya alguien dentro de la RC.
• Como resultado, devuelve 0 si no hubo error, o diferente de 0 si lo hubo.
UNIVERSIDAD NACIONAL DE SAN AGUSTIN
FACULTAD DE INGENIERÍA DE PRODUCCIÓN Y SERVICIOS
ESCUELA PROFESIONAL DE INGENIERÍA DE SISTEMA
Variables globales:
pthread_mutex_t mutex_acceso;
int i;
int main(void)
UNIVERSIDAD NACIONAL DE SAN AGUSTIN
FACULTAD DE INGENIERÍA DE PRODUCCIÓN Y SERVICIOS
ESCUELA PROFESIONAL DE INGENIERÍA DE SISTEMA
{
...
pthread_mutex_init(&mutex_acceso, NULL);
...
}
En color azul han sido añadidas las líneas que antes no estaban.
Como se puede ver lo único que hay que hacer es inicializar el semáforo, pedir el bloqueo antes de las RC y
liberarlo después de salir de la RC, aunque a veces es cuestión también de tener un poco de vista.
Contra más pequeñas hagamos las RC, más concurrentes serán nuestros programas, porque tendrán que
esperar menos tiempo en el caso de que haya bloqueos.
Deadlocks
Aunque esto realmente soluciona el problema de los accesos concurrentes, también nos puede traer más
problemas.
Y los problemas aquí también tienen nombre: los Deadlocks (o Abrazos Mortales) Los Deadlocks se producen
cuando un hilo se bloquea esperando un recurso que tiene bloqueado otro hilo que está esperando un
recurso. Si el recurso para el segundo thread no llega nunca, no se desbloqueará nunca, con lo cual tampoco
se desbloqueará nunca el primer thread.
Resultado: nuestro fantástico programa bloqueado.
Solución: Pues aunque la librería de Pthreads nos de algún mecanismo para intentar prevenir que esto se
produzca, no hay ningún mecanismo fiable al 100% para prevenirlos.
El modelo más sencillo de Deadlock es el circular:
Hilo 1
void *funcion_hilo_1(void *arg)
{
...
pthread_mutex_lock(&mutex_1);
...
pthread_mutex_unlock(&mutex_2);
...
}
Hilo 2
void *funcion_hilo_2(void *arg)
{
...
pthread_mutex_lock(&mutex_2);
...
pthread_mutex_unlock(&mutex_1);
...
}
II. EJERCICIO RESUELTO
Ejercicio 1.
UNIVERSIDAD NACIONAL DE SAN AGUSTIN
FACULTAD DE INGENIERÍA DE PRODUCCIÓN Y SERVICIOS
ESCUELA PROFESIONAL DE INGENIERÍA DE SISTEMA
Crear dos threads en un programa en C utilizando pthread, guardar el programa con el nombre de
ejercicio01.c:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
pthread_t tid[2];
int counter;
return NULL;
}
int main(void)
{
int i = 0;
int error;
while (i < 2) {
error = pthread_create(&(tid[i]), NULL, &trythis, NULL);
if (error != 0)
printf("\nThread no puede ser creado : [%s]", strerror(error));
i++;
}
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
return 0;
}
Ejercicio 2.
Crear dos threads en un programa en C utilizando pthread, guardar el programa con el nombre de
ejercicio02.c:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
pthread_t tid[2];
int counter;
pthread_mutex_t lock;
int main(void)
{
int i = 0;
int error;
if (pthread_mutex_init(&lock, NULL) != 0) {
printf("\n el inicio de mutex ha fallado\n");
return 1;
}
while (i < 2) {
error = pthread_create(&(tid[i]),
NULL,
&trythis, NULL);
if (error != 0)
printf("\nThread can't be created :[%s]",
strerror(error));
i++;
}
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
pthread_mutex_destroy(&lock);
UNIVERSIDAD NACIONAL DE SAN AGUSTIN
FACULTAD DE INGENIERÍA DE PRODUCCIÓN Y SERVICIOS
ESCUELA PROFESIONAL DE INGENIERÍA DE SISTEMA
return 0;
}
Ejercicio 3.
Crear dos threads en un programa en C utilizando pthread, guardar el programa con el nombre de
ejercicio03.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
int main() {
pthread_t t1, t2;
int iret1 = pthread_create(&t1, NULL, print_i, NULL);
int iret2 = pthread_create(&t2, NULL, print_j, NULL);
while(1){}
exit(0); //nunca alcanzado
UNIVERSIDAD NACIONAL DE SAN AGUSTIN
FACULTAD DE INGENIERÍA DE PRODUCCIÓN Y SERVICIOS
ESCUELA PROFESIONAL DE INGENIERÍA DE SISTEMA
Ejercicio 4.
Crear el siguiente programa en c, donde se mostrará en orden los mensajes de las funciones T1 y T2, usando
la cerradura mutex
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
pthread_mutex_t ml;
void* T1(void* z) {
Test("Inicializado", "1er Thread"); }
void* T2(void* z) {
Test("Inicializado ", "2do Thread"); }
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, &T1, NULL);
pthread_create(&t2, NULL, &T2, NULL);
while(1) {}
exit(0);
}
Ejercicio 5.
Crear el siguiente programa en c, donde se presenta en orden los mensajes creados por cada thread,
guárdelo como ejercicio05.c
UNIVERSIDAD NACIONAL DE SAN AGUSTIN
FACULTAD DE INGENIERÍA DE PRODUCCIÓN Y SERVICIOS
ESCUELA PROFESIONAL DE INGENIERÍA DE SISTEMA
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#ifndef NUM_THREADS
#define NUM_THREADS 4
#endif
int shared = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int main() {
pthread_t threads[NUM_THREADS];
printf("%d\n", shared);
exit(EXIT_SUCCESS);
}
Ejercicio 6.
Crear el siguiente programa en c, este programa crea dos archivos utlizando la función fopen() y fclose(),
llamelo ejercicio06.c, compílelo
# include <stdio.h>
# include <stdlib.h>
UNIVERSIDAD NACIONAL DE SAN AGUSTIN
FACULTAD DE INGENIERÍA DE PRODUCCIÓN Y SERVICIOS
ESCUELA PROFESIONAL DE INGENIERÍA DE SISTEMA
# include <pthread.h>
# include <ctype.h>
int total_words ;
pthread_mutex_t counter_lock = PTHREAD_MUTEX_INITIALIZER ;
int main (int ac , char *av[])
{ pthread_t t1 , t2 ;
void *count_words(void *);
if ( ac != 3 ) {
printf("usage : %s file1 file2 \n" , av[0]) ;
exit(1); }
total_words =0;
pthread_create(&t1 , NULL, count_words, (void *)av[1]) ;
pthread_create(&t2 , NULL, count_words, (void *)av[2]) ;
pthread_join(t1 , NULL);
pthread_join(t2 , NULL);
printf("Main thread wirth ID % ld reporting %5 d total words \n ", pthread_self(), total_words);
}
void *count_words (void *f)
{
char *filename = (char *)f;
FILE *fp ; int c , prevc = '\0 ';
if ( ( fp = fopen ( filename ,"r") ) != NULL ){
while ( ( c = getc ( fp ) ) != EOF ){
if ( ! isalnum ( c) && isalnum ( prevc ) ){
pthread_mutex_lock(&counter_lock ) ;
total_words ++;
pthread_mutex_unlock(&counter_lock );
}
prevc = c;
}
fclose(fp);
} else perror(filename);
return NULL ;
}
Ejercicio 7
Crear el siguiente programa en c, que cancela la ejecución de un thread usando la función cancel(), llamelo
ejercicio07.c, compílelo
#include<pthread.h>
#include<stdio.h>
#include<unistd.h>
void *fun1();
UNIVERSIDAD NACIONAL DE SAN AGUSTIN
FACULTAD DE INGENIERÍA DE PRODUCCIÓN Y SERVICIOS
ESCUELA PROFESIONAL DE INGENIERÍA DE SISTEMA
void *fun2();
int shared=1; //variable compartida
pthread_mutex_t l; //mutex lock
int main()
{
pthread_mutex_init(&l, NULL); //inicializando mutex locks
pthread_t thread1, thread2;
pthread_create(&thread1, NULL, fun1, NULL);
pthread_create(&thread2, NULL, fun2, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2,NULL);
printf("El valor final de la variable compartida es %d\n",este); //imprime la ultima actualización del valor de
la variable compartida
}
void *fun1()
{
int x;
printf("Thread1 intentando adquirir la cerradura \n");
pthread_mutex_lock(&l);
//thread uno adquiriendo la cerradura. Ahora los otros threads no serán capacesde de adquirir la cerradura
//hasta que sea liberado por el threada 1
printf("Thread1 adquiere la cerradura\n");
x=shared;//el thread lee el valor de la variable compartida
printf("Thread1 lee el valor de la variable compartida %d\n",x);
x++; //thread uno incremente al valor
printf("Actualizacionón local del Thread1: %d\n",x);
sleep(1); //thread uno es prevenido por thread 2
shared=x; //thread uno actualiza el valor de la variable compartida
printf("Valor de la variable compartida actualizada del Thread1 es: %d\n",shared);
pthread_mutex_unlock(&l);
printf("Thread1 libera la cerradura \n");
return NULL;
}
void *fun2()
{
int y;
printf("Thread2 intenta adquiriri la cerradura \n");
pthread_mutex_lock(&l);
printf("Thread2 adquiere la cerradura \n");
y=shared;//thread dos lee el valor de la variable compartida
printf("Thread2 lee el valor de %d\n",y);
y--; //thread two incrementa el valor
printf("Actualización Local del Thread2: %d\n",y);
sleep(1); //thread dos es prevenido por thread 1
shared=y; //thread uno es actualiza el valor de la variable compartida
printf("Valor de la variable compartida actualizado por el Thread2 es: %d\n",shared);
pthread_mutex_unlock(&l);
UNIVERSIDAD NACIONAL DE SAN AGUSTIN
FACULTAD DE INGENIERÍA DE PRODUCCIÓN Y SERVICIOS
ESCUELA PROFESIONAL DE INGENIERÍA DE SISTEMA
Ejercicio 8.
Crear el siguiente programa en c, que implementa un deadlock, llamelo ejercicio08.c, compílelo
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
int main() {
pthread_t t1, t2;
int iret1 = pthread_create(&t1, NULL, print_i, NULL);
int iret2 = pthread_create(&t2, NULL, print_j, NULL);
while(1){}
exit(0); //nunca alcanzado.
}
Para compilar este programa utilizamos el siguiente comando en linea de comandos
UNIVERSIDAD NACIONAL DE SAN AGUSTIN
FACULTAD DE INGENIERÍA DE PRODUCCIÓN Y SERVICIOS
ESCUELA PROFESIONAL DE INGENIERÍA DE SISTEMA
Ejercicio 9
Crear el siguiente programa en c, que coordina más de dos threads, llamelo ejercicio09.c, compílelo
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
int mails = 0;
pthread_mutex_t mutex;
void* routine() {
for (int i = 0; i < 10000000; i++) {
pthread_mutex_lock(&mutex);
mails++;
pthread_mutex_unlock(&mutex);
// lee mails
// incrementa
// escribe mails
}
}
if (pthread_join(p4, NULL) != 0) {
return 8;
}
pthread_mutex_destroy(&mutex);
printf("Numero de mails: %d\n", mails);
return 0;
}
Ejercicio 10
Crear el siguiente programa en c, que compara dos threads, llamelo ejercicio10.c, compílelo
#include <stdio.h>
#include <pthread.h>
#define NC "\e[0m"
#define YELLOW "\e[33m"
#define BYELLOW "\e[1;33m"
#define RED "\e[31m"
#define GREEN "\e[32m"
tid = pthread_self();
counter = (t_counter *)data;
// Imprime el recuento antes de que este hilo comience a iterar.
// Para leer el valor de count, bloqueamos el mutex:
pthread_mutex_lock(&counter->count_mutex);
UNIVERSIDAD NACIONAL DE SAN AGUSTIN
FACULTAD DE INGENIERÍA DE PRODUCCIÓN Y SERVICIOS
ESCUELA PROFESIONAL DE INGENIERÍA DE SISTEMA
int main(void)
{
pthread_t tid1;
pthread_t tid2;
// Esta estructura continene el contador total del thread
t_counter counter;
// Aquí solo hay un hilo (hilo principal), por lo que podemos hacerlo de forma segura
// inicializa el recuento sin utilizar el mutex.
counter.count = 0;
// Inicializa el mutex :
pthread_mutex_init(&counter.count_mutex, NULL);
// Dado que cada hilo cuenta TIMES_TO_COUNT veces y eso
// tenemos 2 hilos, esperamos que el recuento final sea
// 2 * VECES_PARA_CONTAR:
printf("Main: el contador esperado es %s%u%s\n", GREEN,
2 * TIMES_TO_COUNT, NC);
// Creación del Thread:
pthread_create(&tid1, NULL, thread_routine, &counter);
printf("Main: Creando el primer thread [%ld]\n", tid1);
pthread_create(&tid2, NULL, thread_routine, &counter);
printf("Main: Creando el segundo thread [%ld]\n", tid2);
// Thread adicionado:
pthread_join(tid1, NULL);
UNIVERSIDAD NACIONAL DE SAN AGUSTIN
FACULTAD DE INGENIERÍA DE PRODUCCIÓN Y SERVICIOS
ESCUELA PROFESIONAL DE INGENIERÍA DE SISTEMA
¿Cuántos lock mutex son necesarios para sincroninar los hilos?, impleméntelo
IV. CUESTIONARIO
1. ¿Qué hace la función pthread_mutex_trylock de la librería pthread?
2. ¿Un semáforo es lo mismo que un lock mutex?
contestadas,
como mínimo con
dos frases cada
una (3)
Calidad de La información La información está La información La información está
información tiene poco que ver relacionada con el tema está claramente claramente relacionada
con el tema principal (2) relacionada con el con el tema principal y
principal (0) tema principal (3) presenta otros ejemplos
(6)
Calidad del No cumplió con El ejercicio fue Se llega a una Se llega a una solución
ejercicio realizar el ejercicio desarrollado pero no se solución utilizando del
planteado(0) llego a una solución o al utilizando el procedimiento sugerido
objetivo pedido(2) procedimiento en pocas líneas de
sugerido(3) código(4)