310 likes | 531 Views
PROGRAMACIÓN PARALELA Tema 2: Lenguajes y modelos de programación paralela. Programación con THREADS David R. Butenhof: Programming with POSIX Threads. Addison-Wesley. 1996. Cap 1, 2, 3, 4 Ejemplos en: www.awl.com/cseng/titles/0-201-63392-2. Threads/Procesos. Thread tiene:
E N D
PROGRAMACIÓN PARALELATema 2: Lenguajes y modelos de programación paralela • Programación con THREADS • David R. Butenhof: Programming with POSIX Threads. Addison-Wesley. 1996. • Cap 1, 2, 3, 4 • Ejemplos en: • www.awl.com/cseng/titles/0-201-63392-2 Programación Paralela Programación con Threads 1
Threads/Procesos • Thread tiene: • Dirección de instrucción • Pila • Registro de datos • Proceso tiene: • Lo del thread + • Espacio de direcciones • Descriptores de ficheros • Threads en un proceso comparten: • Espacio de direcciones • Descriptores de ficheros Programación Paralela Programación con Threads 2
Elementos de programación con Threads • Thread: • Entidad capaz de actividad independiente • Elementos compartidos: • Memoria • Objetos para sincronización (mutex) • Mecanismos de comunicación: • Indicar cambios en datos compartidos (variables condición) • Los threads esperan a que se cumpla una condición • Se pueden señalar o hacer broadcast Programación Paralela Programación con Threads 3
Control de concurrencia • Contexto de ejecución: Debe mantenerse independiente • Política de Scheduling: Determina qué contexto se ejecuta en cada instante y el paso de uno a otro • Sincronización: Mecanismos para coordinar el uso de recursos compartidos: mutex, variables de condición, semáforos, eventos, mensajes, ... Programación Paralela Programación con Threads 4
Uso de Threads • Beneficios: • Explotación del paralelismo • Explotación de concurrencia (I/O) • Estilo de programación • Inconvenientes: • Overhead por creación de threads • Sincronización: más bloqueos al haber más threads • Colisiones en el acceso a memoria • Más difícil la depuración: debuggers, trazadores, puntos de ruptura, repreducción de la ejecución, ... • Uso de programación con threads: • Computación intensiva • Varios procesadores • Solapamiento computación y I/O • Aplicaciones servidor distribuidas Programación Paralela Programación con Threads 5
Ejemplo alarm secuencial Programa: Un único proceso que procesa comandos simples en los que se dice que recuerde una alarma después de un intervalo de tiempo. Inconveniente: Sólo se puede procesar una alarma detrás de otra. Programación Paralela Programación con Threads 6
int main (int argc, char *argv[]) { int seconds; char line[128]; char message[64]; while (1) { printf ("Alarm> "); if (fgets (line, sizeof (line), stdin) == NULL) exit (0); if (strlen (line) <= 1) continue; /* Parse input line into seconds (%d) and a message * (%64[^\n]), consisting of up to 64 characters * separated from the seconds by whitespace. */ if (sscanf (line, "%d %64[^\n]", &seconds, message) < 2) { fprintf (stderr, "Bad command\n"); } else { sleep (seconds); printf ("(%d) %s\n", seconds, message); } } } Programación Paralela Programación con Threads 7
Ejemplo alarm_fork, procesos Programa: el proceso principal lee peticiones de alarma, pone en marcha un proceso hijo por cada petición de alarma, cada proceso hijo espera el tiempo correspondiente y escribe el mensaje de alarma. Programación Paralela Programación con Threads 8
#include <sys/types.h> #include <wait.h> int main (int argc, char *argv[]) { int status; char line[128]; int seconds; pid_t pid; char message[64]; ... else { pid = fork (); if (pid == (pid_t)-1) errno_abort ("Fork"); if (pid == (pid_t)0) { /*If we're in the child, wait and then print a message*/ sleep (seconds); printf ("(%d) %s\n", seconds, message); exit (0); } else { /*In the parent, call waitpid() to collect any children that have already terminated*/ do { pid = waitpid ((pid_t)-1, NULL, WNOHANG); if (pid == (pid_t)-1) errno_abort ("Wait for child"); } while (pid != (pid_t)0); } } } } Programación Paralela Programación con Threads 9
Ejemplo alarm_thread, threads Programa: Como el de procesos pero usando threads. Usa: • pthread_create, para crear un thread e indicar la rutina que tiene que ejecutar, • pthread_detach, para retomar los recursos una vez acaba el thread, • pthread_exit, para acabar el thread Programación Paralela Programación con Threads 10
#include <pthread.h> typedef struct alarm_tag { int seconds; char message[64]; } alarm_t; /*Rutina que usan los threads*/ void *alarm_thread (void *arg) { alarm_t *alarm = (alarm_t*)arg; int status; status = pthread_detach (pthread_self ()); if (status != 0) err_abort (status, "Detach thread"); sleep (alarm->seconds); printf ("(%d) %s\n", alarm->seconds, alarm->message); free (alarm); return NULL; } Programación Paralela Programación con Threads 11
int status;char line[128];alarm_t *alarm;pthread_t thread; while (1) { ... alarm = (alarm_t*)malloc (sizeof (alarm_t)); if (alarm == NULL) errno_abort ("Allocate alarm"); if (sscanf (line, "%d %64[^\n]", &alarm->seconds, alarm->message) < 2) { fprintf (stderr, "Bad command\n"); free (alarm); } else { status = pthread_create (&thread, NULL, alarm_thread, alarm); if (status != 0) err_abort (status, "Create alarm thread"); } } Programación Paralela Programación con Threads 12
Uso de threads pthread_t thread; int pthread_equal (pthread_t t1,pthread_t t2); int pthread_create (pthread_t *thread, const pthread_attr_t *attr, void *(*start)(void *),void *arg); Int sched_yield (void); pthread_t pthread_self (void); int pthread_exit (void *value_ptr); int pthread_detach (pthread_t thread); int pthread_join (pthread_t thread,void **value_ptr); Programación Paralela Programación con Threads 13
Uso de threads • Un thread se representa por un identificador, de tipo pthread_t • Un thread se pone en marcha llamando a la función cuya dirección se pasa como tercer parámetro de pthread_create. Esta función recibe un argumento de tipo void * y devuelve lo mismo. Devuelve un identificador de thread. • pthread_self lo puede usar un thread para conocer su identificador. • pthread_equal se usa para comprobar si dos indentificadores de thread se refieren al mismo thread. • pthread_detach se usa para decir al sistema que puede utilizar los recursos que se asignaron al thread. El detach lo puede hacer cualquier thread que sepa su identificador de thread. En la creación se puede especificar con un atributo que no se quiere tener control sobre el thread. • pthread_join se usa para bloquear hasta que acaba el thread. Libera los recursos del thread. Programación Paralela Programación con Threads 14
Sincronización • Se puede sincronizar asegurando el acceso en exclusión mutua a variables. Se usan mutex. • O usando variables condición, que comunican información sobre el estado de datos compartidos Programación Paralela Programación con Threads 15
Creación y destrucción de mutex • pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; Un mutex se representa por una variable de tipo pthread_mutex_t. No se debe hacer copias de mutex, pero sí se pueden copiar punteros a mutex, para usarlos varios threads. Para crear un mutex estático con atributos por defecto se usa la macro PTHREAD_MUTEX_INITIALIZER. • int pthread_mutex_init (pthread_mutex_t *mutex, pthread_mutexattr_t *attr); Se usa para inicializar un mutex dinámicamente o con atributos que no son los de defecto. Cada mutex se debe inicializar una única vez antes de usarse. • int pthread_mutex_destroy (pthread_mutex_t *mutex); Para destruir un mutex. Lo normal es asociar el mutex con los datos que protege, y destruir el mutex antes de liberar la memoria de esos datos. Programación Paralela Programación con Threads 16
Bloqueo y desbloqueo de mutex • int pthread_mutex_lock (pthread_mutex_t *mutex); Bloquea un mutex. No se puede bloquear un mutex cuando el thread ya lo tiene bloqueado. Esto devuelve un código de error, o se bloquea el thread. • int pthread_mutex_unlock (pthread_mutex_t *mutex); Para desbloquear mutex. No se puede desbloquear un mutex desbloqueado, ni un mutex bloqueado por otro thread, pues los mutex pertenecen a los threads que los bloquean. • int pthread_mutex_trylock (pthread_mutex_t *mutex); Bloquea un mutex si no está bloqueado. Programación Paralela Programación con Threads 17
Ejemplo alarm_mutex • Se utiliza una lista de alarmas, ordenadas por tiempo de expiración. typedef struct alarm_tag { struct alarm_tag *link; int seconds; time_t time; /* seconds from EPOCH */ char message[64]; } alarm_t; pthread_mutex_t alarm_mutex=PTHREAD_MUTEX_INITIALIZER; alarm_t *alarm_list = NULL; Programación Paralela Programación con Threads 18
Ejemplo alarm_mutex • Se usa un único thread para tratar las alarmas. • Acaba cuando acaba el thread principal. Se podría modificar usando una variable que indique que se ha acabado el trabajo. Cuando el thread encuentra la lista vacía y esa variable a fin puede acabar con pthread_exit. • Con sched_yield se cede el procesador a otro thread, y si no hay ninguno esperando vuelve el control al que llama. Programación Paralela Programación con Threads 19
void *alarm_thread (void *arg) { alarm_t *alarm;int sleep_time;time_t now;int status; /*Bucle infinito para procesar los comandos*/ while (1) {status = pthread_mutex_lock (&alarm_mutex); if (status != 0)err_abort (status, "Lock mutex"); alarm = alarm_list; if (alarm == NULL)sleep_time = 1; /*Si vacía esperar un segundo*/ else {alarm_list = alarm->link;now = time (NULL); /*Si no tomar el primero*/ if (alarm->time <= now)sleep_time = 0; /*Y calcular espera*/ elsesleep_time = alarm->time - now;} status = pthread_mutex_unlock (&alarm_mutex); if (status != 0)err_abort (status, "Unlock mutex"); if (sleep_time > 0)sleep (sleep_time); elsesched_yield (); /*If a timer expired, print the message and free thestructure.*/ if (alarm != NULL) { printf ("(%d) %s\n", alarm->seconds, alarm->message); free (alarm); }}} Programación Paralela Programación con Threads 20
int main (int argc, char *argv[]) { int status;char line[128];alarm_t *alarm,**last,*next;pthread_t thread; status = pthread_create (&thread, NULL, alarm_thread, NULL); if (status != 0)err_abort (status, "Create alarm thread"); while (1) {printf ("alarm> "); if (fgets (line, sizeof (line), stdin) == NULL) exit (0); if (strlen (line) <= 1) continue; alarm = (alarm_t*)malloc (sizeof (alarm_t)); if (alarm == NULL)errno_abort ("Allocate alarm"); if (sscanf (line,"%d %64[^\n]",&alarm->seconds,alarm->message)<2) {fprintf (stderr, "Bad command\n");free (alarm); } else {status = pthread_mutex_lock (&alarm_mutex); if (status != 0)err_abort (status, "Lock mutex"); alarm->time = time (NULL) + alarm->seconds; last = &alarm_list; /*Insertar la nueva alarma*/ next = *last; while (next != NULL) { if (next->time >= alarm->time) { alarm->link = next;*last = alarm;break;} last = &next->link;next = next->link;} if (next == NULL) {*last = alarm;alarm->link = NULL;} status = pthread_mutex_unlock (&alarm_mutex); if (status != 0)err_abort (status, "Unlock mutex");}}} Programación Paralela Programación con Threads 21
Ejemplo alarm_mutex • Problemas: • Una vez se toma una alarma de la cola se espera hasta que expira. • Cuando no hay alarmas que atender espera un segundo. Durante este tiempo puede venir otra alarma que haya que atender. • Solución: Usar variables condición. Programación Paralela Programación con Threads 22
Creación y destrucción de variables condición • pthread_cond_t cond=PTHREAD_COND_INITIALIZER; Una variable condición se representa por una variable de tipo pthread_cond_t. No se debe hacer copias de variables condición, pero sí se pueden copiar punteros a ellas, para usarlas varios threads. Para crear una variable condición estática con atributos por defecto se usa la macro PTHREAD_COND_INITIALIZER. • int pthread_cond_init (pthread_cond_t *cond, pthread_condattr_t *attr); Se usa para inicializar una variable condición dinámicamente o con atributos que no son los de defecto. Cada variable condición se debe inicializar una única vez antes de usarse. • int pthread_cond_destroy (pthread_cond_t *cond); Para destruir una variable condición. Lo normal es asociar el mutex y la variable condición con los datos que protegen. Programación Paralela Programación con Threads 23
Espera en variables condición • int pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex); Cada variable condición se asocia a un mutex. Cuando un thead espera en una variable condición el mutex debe estar bloqueado. La operación wait desbloquea el mutex antes de bloquear el thread, y lo bloquea antes de volver a la ejecución. El predicado se debe testear después de bloquear el mutex y antes de esperar en la varaible condición. El predicado se debe testear al ser activado. • int pthread_cond_timedwait (pthread_cond_t *cond, pthread_mutex_t *mutex, struct timespec *expiration); Espera como mucho el tiempo que se indica. Programación Paralela Programación con Threads 24
Activación de variables condición • Int pthread_cond_signal (pthread_cond_t *cond); Activa un thread que espera en la variable condición. • Int pthread_cond_broadcast (pthread_cond_t *cond); Activa todos los threads que esperan en la variable condición. Cuando los threads se activan tendrán que volver a evaluar el predicado y algunos de ellos posiblemente se volverán a bloquear. Programación Paralela Programación con Threads 25
Ejemplo alarm_cond • El thread está en una variable condición mientras espera para dar la alarma. • Si el main procesa una nueva alarma activa al threads para que compare la alarma que está esperando a procesar con la nueva alarma. • Se usa una función para insertar en la lista de alarmas. Será llamada por el main y el thread. Programación Paralela Programación con Threads 26
Ejemplo alarm_cond typedef struct alarm_tag { struct alarm_tag *link; int seconds; time_t time; /* seconds from EPOCH */ char message[64]; } alarm_t; pthread_mutex_talarm_mutex=PTHREAD_MUTEX_INITIALIZER; pthread_cond_t alarm_cond=PTHREAD_COND_INITIALIZER; alarm_t *alarm_list = NULL; time_t current_alarm = 0; Programación Paralela Programación con Threads 27
void alarm_insert (alarm_t *alarm) { int status; alarm_t **last, *next; /*Es necesario que el thread que llama haya bloqueado el mutex*/ last = &alarm_list;next = *last; while (next != NULL) { if (next->time >= alarm->time) { alarm->link = next;*last = alarm;break; } last = &next->link; next = next->link; } if (next == NULL) { *last = alarm;alarm->link = NULL; } /*Activar el thread si no está ocupado o la nueva alarma es la primera*/ if (current_alarm == 0 || alarm->time < current_alarm) { current_alarm = alarm->time; status = pthread_cond_signal (&alarm_cond); if (status != 0) err_abort (status, "Signal cond"); } } Programación Paralela Programación con Threads 28
void *alarm_thread (void *arg) { alarm_t *alarm; struct timespec cond_time; time_t now; int status, expired; status = pthread_mutex_lock (&alarm_mutex); if (status != 0) err_abort (status, "Lock mutex"); while (1) { current_alarm = 0; while (alarm_list == NULL) { status = pthread_cond_wait (&alarm_cond, &alarm_mutex); if (status != 0) err_abort (status, "Wait on cond"); } alarm = alarm_list; alarm_list = alarm->link; now = time (NULL); expired = 0; if (alarm->time > now) { cond_time.tv_sec = alarm->time; cond_time.tv_nsec = 0; current_alarm = alarm->time; while (current_alarm == alarm->time) { status = pthread_cond_timedwait (&alarm_cond, &alarm_mutex, &cond_time); if (status == ETIMEDOUT) { expired = 1; break; } if (status != 0) err_abort (status, "Cond timedwait"); } if (!expired) alarm_insert (alarm); } else expired = 1; if (expired) { printf ("(%d) %s\n", alarm->seconds, alarm->message); free (alarm); } } } Programación Paralela Programación con Threads 29
int main (int argc, char *argv[]) {int status;char line[128];alarm_t *alarm;pthread_t thread; status = pthread_create (&thread, NULL, alarm_thread, NULL); if (status != 0)err_abort (status, "Create alarm thread"); while (1) {printf ("Alarm> "); if (fgets (line, sizeof (line), stdin) == NULL) exit (0); if (strlen (line) <= 1) continue; alarm = (alarm_t*) malloc (sizeof (alarm_t)); if (alarm == NULL) errno_abort ("Allocate alarm"); if (sscanf (line, "%d %64[^\n]",&alarm->seconds, alarm->message) < 2) { fprintf (stderr, "Bad command\n"); free (alarm); } else {status = pthread_mutex_lock (&alarm_mutex); if (status != 0)err_abort (status, "Lock mutex"); alarm->time = time (NULL) + alarm->seconds; alarm_insert (alarm); status = pthread_mutex_unlock (&alarm_mutex); if (status != 0) err_abort (status, "Unlock mutex"); } } } Programación Paralela Programación con Threads 30
Modelos de programación En el capítulo 4, con ejemplos. Utilizar el que se crea conveniente para programar en threads la primera práctica. • Pipeline: Cada thread ejecuta la misma operación sobre secuencia de datos de entrada, y pasa el resultado a otro thread. • Granja de trabajadores: Los threads trabajan independientemente sobre datos distintos. • Cliente/servidor: Un cliente pide a un servidor que haga un trabajo. Puede ser una petición anónima que se manda a una cola de trabajos. Programación Paralela Programación con Threads 31