Nothing Special   »   [go: up one dir, main page]

Tema 1 - Threads - Apunte PTHREADS PDF

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 7

Sistemas Operativos

Materia: Sistemas Operativos – Fac. de Informática – U.N.L.P.


Unidad: Threads (Hilos)
Versión: Marzo 2009
Autores: Baez, Martín - Perez, Juan Pablo
Palabras Claves: Hilos, Threads, Linux, POSIX, Pthreads, Semaforos, Mutexs

Pthreads
Pthreads es una librería que cumple los estándares POSIX y que nos permite trabajar con distintos hilos
de ejecución(threads) al mismo tiempo en un mismo proceso( programa).

El standard POSIX
POSIX es el acrónimo de Portable Operating System Interface, viniendo la X de UNIX..
Estos son una familia de standares de llamadas al sistema que tienen como finalidad generalizar las
interfaces de los sistemas operativos para que las aplicaciones se ejecuten en distintas plataformas.

Como compilar un programa con pthreads


Al compilar un programa hay que “decirle” al compilador que nuestro programa usará threads.
La forma más usual de hacer esto es, si estamos usando como compilador GNU gcc con el comando:

gcc prog_con_pthreads.c -o prog_con_pthreads -lpthread

Si por el contrario no estamos usando el compilador de GNU, lo mejor sera que miremos la pagina man
del compilador de C instalado en el sistema. Por ejemplo, en el caso del compilador cc de la mayoria
de las distribuciones de linux:
cc prog_con_pthreads.c -o prog_con_pthreads -pthread

RECORDAR: Importar siempre la libreria de threads, en la sección sw includes de nuestro programa


que usa threads -> #include <pthread.h>

Creación ejecución y manipulación de threads

✔ Creación: Para ello nos valemos de:

Función pthread_create.
El prototipo de la función es el siguiente:
int pthread_create(pthread_t * thread, pthread_attr_t *attr, void * (*start_routine)(void *), void *arg)

✔ thread: Es una variable del 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 libreria para llevar a cabo
alguna acción sobre él.
✔ attr: Es un parámetro del tipo pthread_attr_t y que se debe inicializar previamente con los atributos
que queramos que tenga el thread. Entre los atributos figuiran: la prioridad, el quantum!!,y el
algoritmo de planificación que queramos usar!!!!!. Si pasamos como parámetro aquí NULL, la
librería le asignará al thread unos atributos por defecto..
✔ start_routine: Aquí pondremos la dirección de la función que queremos que ejecute el thread. La
función debe devolver un puntero genérico (void *) como resultado, y debe tener como único
parámetro otro puntero genérico.
✔ arg: Es un puntero al parámetro que se le pasará a la función. Puede ser NULL si no queremos
pasarle nada a la función.

Como siempre en C en caso de que todo haya salido bien, la función devuelve un 0 o un valor distinto
de 0 en caso de que hubo algun error.

Una vez tenemos los threads creados tenemos dos opciones:


1. Esperar a que terminen los threads, en el caso de que nos interese recoger algun resultado
2. Decirle a la librería de pthreads que cuando termine la ejecución de la función del thread elimine
todos sus datos de sus tablas internas.

Para que nuestro programa se comporte según las anteriores opciones disponemos de dos funciones
más de la librería: pthread_join y pthread_detach.

Función pthread_join:Esta función suspende el thread llamante hasta que no termine su ejecución el
thread indicado por th. Además, una vez éste último termina, pone en thread_return el resultado
devuelto por el thread que se estaba ejecutando.

El prototipo de la función es el siguiente:


int pthread_join(pthread_t th, void **thread_return)

➢ th: Es el identificador del thread que queremos esperar, y es el mismo que obtuvimos al crearlo con
pthread_create.
➢ thread_return: Es un puntero a un puntero que apunta (valga la redundancia) al resultado devuelto
por el thread que estamos esperando cuando terminó su ejecución. Si este parámetro es NULL, le
estamos indicando a la librería que no nos importa el resultado.

Función pthread_detach:Esta función es para desligar a un proceso de los threads que haya creado .
Le indica a la librería que NO queremos que nos guarde el resultado de la ejecución del thread indicado
por th. Por defecto la librería guarda el resultado de ejecución de todos los threads hasta que nosotros
hacemos un pthread_join para recoger el resultado. De esta manera una vez que el thread haya
terminado la librería eliminará los datos del thread de sus tablas internas y tendremos más espacio
disponible para crear otros threads

El prototipo de la función es el siguiente:


int pthread_detach(pthread_t th)
✔ th: Es el identificador del thread

Ejemplo: Si quisieramos crear 2 hilos de ejecución deiferentes de la función miFuncion deberiamos


codificar lo siguiente:

Sistema Operatvivos 2006 – Threads – Pthreads Página 2 de 7


#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void *mi_funcion( void *ptr );

main()
{
pthread_t thread1, thread2;
char *message1 = "Thread 1";
char *message2 = "Thread 2";
int iret1, iret2;

/* Creamos los threads. A partir de este momento los threads se ejecutan! */


iret1 = pthread_create( &thread1, NULL, mi_funcion, (void*) message1);
iret2 = pthread_create( &thread2, NULL, mi_funcion, (void*) message2);

/* Aqui podria haber cualquier codigo */

/* Esperamos a que nuestros threads finalizen , esto es bloqueante! */


pthread_join( thread1, NULL);
pthread_join( thread2, NULL);

printf("Fin del Thread 1. Resultado: %d\n",iret1);


printf("Fin del Thread 2. Resultado: %d\n",iret2);
exit(0);
}

void *mi_funcion( void *ptr ){


char *message;
message = (char *) ptr;
printf("%s \n", message);
}

Problemas de concurrencia
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

Mecanismos para prevenir problemas de concurrencia


Lo que Pthreads nos ofrece son los semáforos binarios, semáforos mutex o simplemente mutexs.
El semáforo binario es una estructura de datos que actúa como un semáforo porque puede tener dos
estados: o abierto o cerrado. Cuando el semáforo está abierto, al primer thread que pide un bloqueo se
le asigna ese bloqueo y no se deja pasar a nadie más por el semáforo. Mientras que si el semáforo está
cerrado, porque algun thread ya tiene el bloqueo, el thread que lo pidió parará su ejecución hasta que no
sea liberado el susodicho bloqueo.
Solo puede haber un solo thread poseyendo el bloqueo del semáforo, mientras que puede haber más de
un thread esperando para entrar en la RC, encolados en la cola de espera del semáforo. Es decir, los
threads se excluyen mútuamente (de ahí lo de mutex para el nombre) el uno al otro para entrar.

Las funciones que ofrece Pthreads para llevar esto a cabo son:

Sistema Operatvivos 2006 – Threads – Pthreads Página 3 de 7


✔ pthread_mutex_init
✔ pthread_mutex_lock
✔ pthread_mutex_unlock
✔ pthread_mutex_destroy

Función pthread_mutex_init:Esta función inicializa un mutex. Hay que llamarla antes de usar
cualquiera de las funciones que trabajan con mutex.
El prototipo de la función es el siguiente:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr)
✔ 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 (En este apunte solo veremos semáforos mutex y
recursivos), si este valor es NULL , la librería le asignará un valor por defecto.

Función pthread_mutex_lock: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.
El prototipo de la función es el siguiente:
int pthread_mutex_lock(pthread_mutex_t *mutex)
✔ mutex: Es un puntero al mutex sobre el cual queremos pedir el bloqueo o sobre el que nos
bloquaremos en caso de que ya haya alguien dentro de la RC.

Función pthread_mutex_unlock:sta es la función contraria a la anterior. Libera el bloqueo que


tuviéramos sobre un semáforo.
El prototipo de la función es el siguiente:
int pthread_mutex_unlock(pthread_mutex_t *mutex)
✔ mutex: Es el semáforo donde tenemos el bloqueo y queremos liberarlo.

Función pthread_mutex_destroy:e dice a la librería que el mutex que el estamos indicando no lo


vamos a usar más, y que puede liberar toda la memoria ocupada en sus estructuras internas por ese
mutex.
El prototipo de la función es el siguiente:
int pthread_mutex_destroy(pthread_mutex_t *mutex)
✔ mutex: El mutex que queremos destruir.

Ejemplo de sincronización básica

Thread 1

Sistema Operatvivos 2006 – Threads – Pthreads Página 4 de 7


void *funcion_thread_1(void *arg)
{
...
pthread_mutex_lock(&mutex_1);
/* Comienzo Sección critica de código */
...
/* Fin Sección critica de código */
pthread_mutex_unlock(&mutex_2);
...
}

Thread 2
void *funcion_Thread_2(void *arg)
{
...
pthread_mutex_lock(&mutex_2);
/* Comienzo Sección critica de código */
...
/* Fin Sección critica de código *
pthread_mutex_unlock(&mutex_1);
...
}

Semáforos recursivos
Estos semáforos solo aceptarán una sola petición de bloqueo por el mismo thread. Si el mismo thread
hace 10 llamadas a pthread_mutex_lock sobre el mismo semáforo, luego tendrá que hacer 10 llamadas
a pthread_mutex_unlock, es decir, tantas como haya hecho a pthread_mutex_lock.
En cambio, los del tipo recursivo solo aceptarán una sola llamada a pthread_mutex_lock. Las siguientes
llamadas serán ignoradas.
Para poder crear un semáforo recursivo, tendremos que decírselo a pthread_mutex_init, indicándole
como atributo el resultado de una llamada a pthread_mutexattr_settype. El procedimiento es:

1. Definir una variable del tipo pthread_mutexattr_t:


pthread_mutexattr_t mutex_attr;
2. Inicializarla con la llamada a phtread_mutexattr_init:
pthread_mutexattr_init(&mutex_attr);
3. Indicarle el tipo explícitamente mediante pthread_mutexattr_settype:
thread_mutexattr_settype(&mutex_attr, tipo);
Donde tipo puede ser PTHREAD_MUTEX_NORMAL, PTHREAD_MUTEX_DEFAULT (el que se
usa por defecto), PTHREAD_MUTEX_RECURSIVE o PTHREAD_MUTEX_ERRORCHECK.

Si creemos que la siguiente llamada a pthread_mutex_lock va a ser bloqueante y que puede provocar
un deadlock, la librería de Pthreads nos ofrece una función más para comprobar si eso es cierto:
pthread_mutex_trylock.

Función pthread_mutex_trylock:La función devuelve EBUSY si el el thread llamante se bloqueará o


0 en caso contrario. Si no se produce el bloqueo, la función actúa igual que phtread_mutex_lock,
adquiriendo el bloqueo sobre el semáforo.

El prototipo de la función es el siguiente:


int pthread_mutex_trylock(pthread_mutex_t *mutex);

Sistema Operatvivos 2006 – Threads – Pthreads Página 5 de 7


✔ mutex: Es el semáforo donde tenemos el bloqueo y queremos liberarlo.

Semáforos contadores
La libreria phtreads no contiene un mecanismo de sincronización entre procesos con semáforos para
ello contiene variables condición que es un mecanismo de sincronización similar pero que escapa al
alcance del curso.
Para sincronizar procesos y/o contar recursos utilizaremos otra libreria de semáforos que nos provee de
los semáforos contadores.

Semáforos contadores: Estos semáforos pueden incializarse en un valor mayor a 0. Sirven como
contadores que tiene valores no negativos y que deben iniciarse antes de ser usados. Estos semáforos
existen a en la especificacion de POSIX.1b

En resumen para proteger regiones críticas e implementar exclusión mutua utilizaremos los mutex
de pthreads, para sincronizar procesos, contar recursos.etc utilizaremos la libreria semaphore.h que
figura a partir de la versión 1b del standard POSIX.

¿Porque no utlizar semaforos contadores siempre?


Si bien se podrian usar siempre semáforos contadores para programar exclusión mutua y
sincronización, utlizaremos mutexes para proteger regiones críticas y semáforos contadores para
programar sincronización entre threads, contar recursos,etc . De esta manera el código de los
programas será mas legible y facil de entender ya que es mas facil deducir en el código cuando se trata
de una region crítica y cuando estamos sincronizando la ejecución en threads, contado recursos,etc.

RECORDAR: Importar siempre la libreria de de semaforos, en los imports de nuestro programa que
usa threads -> #include <semaphore.h>
Algunas operaciones permitidas sobre ellos son:

int sem_wait(sem_t * sem);


int sem_post(sem_t * sem);
int sem_destroy(sem_t * sem);
int sem_init(sem_t *sem, int pshared, unsigned int value);

Para utilizar un semaforo de este tipo debemos realizar lo siguiente.

1. Definir una variable del tipo puntero a sem_t(sea sem_t*).


2. Inicializar el semaforo sem_init(sem, share, valor_del_semaforo);

Función sem_init:Esta función inicia al semáforo para que contenga un valor.


El prototipo de la función es el siguiente:
int sem_init(sem_t *sem, int pshared, unsigned int value);
✔ value: Es el valor inicial del semáforo del semáforo, debe ser siempre mayor a 0.
✔ pshared:el valor de pshare NO es 0, el semáforo puede ser usado entre procesos
✔ *sem: Es un puntero a sem_t, el semáforo que estamos incializando.

Sistema Operatvivos 2006 – Threads – Pthreads Página 6 de 7


Función sem_wait:Si el valor del semáforo es cero, entonces sem_wait se bloquea. hasta que alguien
realize una operación sem_post sobre el semaforo.
int sem_wait(sem_t * sem);
✔ *sem: Es un puntero a sem_t, el semáforo que estamos incializando.

Función sem_post:Incrementa el valor del semáforo y es la clásica señal (signal) de operación del
semáforo.
int sem_wait(sem_t * sem);
✔ *sem: Es un puntero a sem_t, el semáforo que estamos incializando.

Sistema Operatvivos 2006 – Threads – Pthreads Página 7 de 7

También podría gustarte