Taller de Programación Paralela Fernando R. Rannou Departamento de Ingenierı́a Informática Universidad de Santiago de Chile April 26, 2007 Concurrencia Concurrencia y sincronización . . . . . . . . . . . . Beneficios de concurrencia . . . . . . . . . . . . . . Ejemplo de problema . . . . . . . . . . . . . . . . . Definición de términos. . . . . . . . . . . . . . . . . Modelo de concurrencia. . . . . . . . . . . . . . . . Requerimientos para una solución . . . . . . . . . Soluciones por hardware 1 . . . . . . . . . . . . . . Soluciones por hardware 2, testset() . . . . . . . EM por instrucciones de máquina . . . . . . . . . Soluciones por software 1 . . . . . . . . . . . . . . Solución de Peterson. . . . . . . . . . . . . . . . . . Semáforos . . . . . . . . . . . . . . . . . . . . . . . . . Primitivas sobre semáforos . . . . . . . . . . . . . . Semáforo binario o mutex . . . . . . . . . . . . . . EM usando semáforos . . . . . . . . . . . . . . . . . El problema del productor/consumidor. . . . . . Solución productor/consumidor con semáforos Sincronización con hebras POSIX . . . . . . . . . Mutex . . . . . . . . . . . . . . . . . . . . . . . . . . . Variables de condición . . . . . . . . . . . . . . . . . Productor/consumidor usando pthreads . . . . . El Productor . . . . . . . . . . . . . . . . . . . . . . . El Consumidor . . . . . . . . . . . . . . . . . . . . . . Barreras para hebras . . . . . . . . . . . . . . . . . . Implementación barreras con pthreads . . . . . . Inicialización de barrera . . . . . . . . . . . . . . . . Espera con barreras . . . . . . . . . . . . . . . . . . Uso de barreras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 Concurrencia 2 / 30 Concurrencia y sincronización ■ Cuando dos o má tareas (procesos o hebras) comparten algún recurso en forma concurrente o paralela, debe existir un mecanismo que sincronice sus actividades ■ El riesgo: corrupción del recurso compartido ■ Ejemplos: 1. Acceso a impresora en un sistema distribuido 2. Consulta y actualización de una base de datos 3. Ejecuci’on paralela de tareas para resolver un problema en conjunto ■ Los problemas de sincronización no sólo ocurren en sistemas multiprocesadores, sino también (y fundamentalmente) en monoprocesadores Fernando R. Rannou Diinf-USACH 3 / 30 Beneficios de concurrencia ■ Aumento en el rendimiento mediante múltiples procesadores u Paralelsimo ■ Aumento del throughput de las aplicaciones u Un llamado a una operación de I/O sólo bloquea una hebra ■ Mejora en interactividad de las aplicaciones u Asignar mayor prioridad a hebras que atiendan al usuario ■ Mejora en la estructuración de aplicaciones ■ Sistemas distribuidos Fernando R. Rannou Diinf-USACH 4 / 30 2 * de problema Ejemplo ■ Considere dos procesos que ejecutan concurrentemente a la funcion echo() del SO: void echo() { chin = getchar(); chout = chin; putchar(chout); } ■ * Asuma que chin es variable compartida Proceso 1 . chin = getchar(); <- interrupcion Proceso 2 . chin = getchar(); chout = chin; <- interrupcion chout = chin; putchar(chout); putchar(chout); ■ Solución: permitir que sólo un proceso ejecute echo() a la vez Fernando R. Rannou Diinf-USACH 5 / 30 Definición de términos ■ Una sección crı́tica (SC) es una sección de código de un proceso o hebra que accesa recursos compartidos, la cual (la sección) no puede ser ejecutada mientras otro proceso está ejecutando su SC correspondiente ■ Por ejemplo, echo() en el ejemplo anterior es SC. ■ Exclusión mútua es el requerimiento que cuando un proceso está en una SC que accesa ciertos recursos compartidos, ningún otro proceso puede estar ejecutando su correspondiente SC en la cual accesa alguno de dichos recursos ■ Deadlock ocurre cuando dos o más procesos no pueden progresar porque cada uno está esperando que otro proceso realize alguna acción ■ Inhanición es cuando un proceso en estado de listo, nunca es planificado en el procesador. Fernando R. Rannou Diinf-USACH 6 / 30 3 Modelo de concurrencia ■ * Asumimos una colección de procesos del tipo Process(i) { while (true) { codigo no critico ... enterSC(); SC(); exitSC(); .. codigo no critico } } ■ enterSC() representa las acciones que el proceso debe realizar para poder entrar a su SC ■ exitSC() representa las acciones que el proceso debe realizar para salir la SC y dejarla habilitada a otros procesos ■ El código no crı́tico es cualquier código que el proceso ejecuta que no accede recursos compartidos Fernando R. Rannou Diinf-USACH 7 / 30 Requerimientos para una solución ■ El objetivo es proveer menanismos que implementen enterSC() y exitSC() que satisfagan los siguientes 3 requerimientos 1. Exclusión mútua: Cuando Pi está en su SC, ningún otro proceso Pj está en su correspondiente SC. Esto significa que la ejecución de operaciones de las secciones crı́ticas no son concurrentes 2. Sin deadlock: Deadlock ocurrirı́a cuando todos los procesos quedan ejecutando enterSC() indefinidamente, y por lo tanto, ninguno entra y ninguno progresa 3. Sin inhanición: Se debe garantizar que un proceso Pi que ejecuta enterSC(), eventualmente lo haga ■ Notas u Un proceso puede tener más de un SC u Libre de deadlock no garantiza que algún proceso se bloquee indefinidamente u Libre de inhanición no dice cuando un proceso entra a la SC u La solución no debe realizar suposiciones sobre la velocidad relativa y orden de ejecución de los procesos Fernando R. Rannou Diinf-USACH 8 / 30 4 Soluciones por hardware 1 ■ Deshabilitación de interrupciones Recordar que un proceso se ejecuta hasta que invoca un llamado al SO o es interrumpido. Entonces para garantizar EM, el proceso deshabilita las interrupciones justo antes de entrar en su SC while (true) { deshabilitar_interrupciones(); SC(); habilitar_interrupciones(); } ■ Habilidad del procesador para hacer context-switch queda limitada ■ No sirve para multiprocesadores Fernando R. Rannou Diinf-USACH 9 / 30 Soluciones por hardware 2, testset() ■ Uso de instrucciones especiales de máquina que realizan en forma atómica más de una operación. ■ Por ejemplo testset() boolean testset(var int i) { if (i == 0) { i = 1; return true; } else return false; } int bolts; P(i) { while (true) { codigo no critico while (!testset(bolt)); SC(); bolt = 0; codigo no critico } } void main() { bolt = 0; parbegin(P(1), P(2), ..., P(n)); } Fernando R. Rannou Diinf-USACH 10 / 30 5 EM por instrucciones de máquina ■ Ventajas u Aplicable a cualquier número de procesos y procesadores que comparten memoria fı́sica u Simple y fácil de verificar u Puede ser usada con varias SC, cada una controlada por su propia variable ■ Desventajas u Busy-waiting (o spin-lock) consume tiempo de procesador u Es posible inhanición Fernando R. Rannou Diinf-USACH 11 / 30 Soluciones por software 1 ■ Dos procesos comparten la variable turno int turno; P(i) { while (true) { codigo no critico while (turno != 0); SC(); turno = 1; codigo no critico } } P(i) { while (true) { codigo no critico while (turno != 1); SC(); turno = 0; codigo no critico } } void main() { turno = 0; parbegin(P(0), P(1)); } ■ Garantiza exclusión mútua ■ Sólo aplicable a dos procesos ■ Alternación estricta de ejecución Fernando R. Rannou Diinf-USACH 12 / 30 6 Solución de Peterson ■ Dos procesos comparten dos variable: flag[2] y turno boolean flag[2]; int turno; P(i) { while (true) { codigo no critico flag[0] = true; turno = 1; while (flag[1] && turno == 1); SC(); flag[0] = false; codigo no critico } } void main(){ flag[0] = flag[1] = false; parbegin(P(0), P(1)); } P(i) { while (true) { codigo no critico flag[1] = true; turno = 0; while (flag[0] && turno == 0); SC(); flag[1] = false; codigo no critico } } Fernando R. Rannou Diinf-USACH 13 / 30 Semáforos ■ Una semáforo es una variable especial usada para que dos o más procesos se señalicen mútuamente ■ Un semáforo es una variable entera, pero ■ Accesada sólo por dos operaciones 1. wait(s) decrementa el semáforo; si el valor resultante es negativo, el proceso se bloquea; sino, continúa su ejecución 2. signal(s) incrementa el semáforo; si el valor resultante es menor o igual que cero, entonces se despierta un proceso que fue bloqueado por wait() ■ Inicializada con un nḿero no negativo Fernando R. Rannou Diinf-USACH 14 / 30 7 Primitivas sobre semáforos struct semaphore { int count; queueType queue; } void wait(semaphore s) { s.count--; if (s.count < 0) { place this process in s.queue; block this process } } void signal(semaphore s) { s.count++; if (s.count <= 0) { remove a process P from s.queue; place process P on ready state } } Fernando R. Rannou Diinf-USACH 15 / 30 Semáforo binario o mutex ■ Un semáforo binario sólo puede tomar los valores 0 o 1 wait(s) { if (s.value == 1) s.value = 0; else { place this process in s.queue block this process } } signal(s) { if (s.queue is empty()) s.value = 1 else { remove a process P from s.queue; place P on the ready list; } } Fernando R. Rannou Diinf-USACH 16 / 30 8 EM usando semáforos semaphore s; Process(i) { while (true) { codigo no critico ... wait(s) SC(); signal(s); .. codigo no critico } } void main() { s = 1; parbegin(P(1), P(2),...,P(n)); } Fernando R. Rannou Diinf-USACH 17 / 30 El problema del productor/consumidor ■ Uno o más procesos productores generan datos y los almacenan en un buffer compartido ■ Un proceso consumidor toma (consume) los datos del buffer uno a la vez ■ Claramente, sólo un agente (productor o consumidor) pueden accesar el buffer a la vez producer() { while (true) { v = produce(); while ((in + 1) % n == out); /* do nothing */ b[in] = v; in = (in + 1) % n } } consumer() { while (true) { while (in == out); /* do nothing */ w = b[out]; out = (out + 1) % n; consume(w); } ■ El buffer es de largo n y tratado circularmente ■ in indica la siguiente posición libre y out el siguiente elemento a consumir Fernando R. Rannou Diinf-USACH 18 / 30 9 Solución productor/consumidor con semáforos const int semaphore semaphore semaphore sizeofbuffer; s = 1; full = 0; empty = sizeofbuffer; producer() { while(true) { v = produce(); wait(empty); wait(s); put_in_buffer(v); signal(s); signal(full); } } consumer() { while(true) { wait(full); wait(s); w = take_from_buffer(); signal(s); signal(empty); consume(w); } } Fernando R. Rannou Diinf-USACH 19 / 30 Sincronización con hebras POSIX ■ Hebras POSIX definen funciones para la sincronización de hebras 1. mutexes: parecidos a los semáforos binarios y sirven para proveer exclusión mútua a cualquier número de hebras de un proceso 2. variables de condición: un mecanismo asociado a los mutexes. Permiten que una hebra se bloquee y libere la SC en la que se encuentra ■ Estos mecanismos no sirven para sincronizar hebras de distintos procesos Fernando R. Rannou Diinf-USACH 20 / 30 10 Mutex ■ Un * mutex es la forma más básica de sincronización y provee el mismo servicio que un semáforo binario ■ Generalmente se utilizan para implementar acceso exclusivo a SC de las hebras #include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; int pthread_mutex_destroy(pthread_mutex_t *mutex); ■ Si una hebra invoca pthread_mutex_lock(m) y el mutex m ya está tomado, entonces la hebra se bloquea (en ese mutex), sino toma (cierra) el mutex y continúa su ejecución ■ Si una hebra invoca pthread_try_lock(m) y el mutex m ya está tomado, entonces pthread_try_lock(m) retorna un código de error EBUSY; la hebra no se bloquea y puede continuar su ejecución (fuera de la SC) Fernando R. Rannou Diinf-USACH 21 / 30 Variables de condición ■ ¿ Qué sucederı́a si una hebra se bloquea dentro de una SC? ■ Una variable de condición permite que una hebra se bloquee dentro de una SC y al mismo tiempo libere la sección crı́tica int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t * mutex); int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond); int pthread_cond_destroy(pthread_cond_t *cond); int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t * attr); pthread_cond_t cond = PTHREAD_COND_INITIALIZER; ■ La hebra que invoca pthread_cond_wait(c, m) se bloquea hasta que la condición c se señalice; además libera el mutex m ■ pthread_cond_signal(c) señaliza o despierta alguna otra hebra que está bloqueada en la variable de condición c ■ pthread_cond_broadcast(c) desbloquea todas las hebras esperando en c Fernando R. Rannou Diinf-USACH 22 / 30 11 Productor/consumidor usando pthreads typedef struct { int buf[QUEUESIZE]; int head, tail; int full, empty; pthread_mutex_t mutex; pthread_cond_t notFull, notEmpty; } buffer_t; void main() { buffer_t *buffer; pthread_t pro, con; buffer = bufferInit(); pthread_mutex_init(&buffer->mutex, NULL); pthread_cond_init(&buffer->notFull, NULL); pthread_cond_init(&buffer->notEmpty, NULL); pthread_create(&pro, NULL, producer, (void *) buffer); pthread_create(&con, NULL, consumer, (void *) buffer); pthread_join (pro, NULL); pthread_join (con, NULL); exit 0; } Fernando R. Rannou Diinf-USACH 23 / 30 El Productor void *producer(void *arg) { int v; buffer_t *buffer; buffer = (buffer_t *) arg; while (true) { v = produce(); pthread_mutex_lock (&buffer->mutex); while (buffer->full) { pthread_cond_wait (&buffer->notFull, &buffer->mutex); } put_in_buffer(buffer, v); pthread_cond_signal(&buffer->notEmpty); pthread_mutex_unlock(&buffer->mutex); } } Fernando R. Rannou Diinf-USACH 24 / 30 12 El Consumidor void *consumer (void *arg) { int v; buffer_t *buffer; buffer = (buffer_t *) arg; while (true) { pthread_mutex_lock (&buffer->mutex); while (buffer->empty) { pthread_cond_wait (&buffer->notEmpty, &buffer->mutex); } v = take_from_buffer(buffer); pthread_cond_signal(&buffer->notFull); pthread_mutex_unlock(&buffer->mutex); } } Fernando R. Rannou Diinf-USACH 25 / 30 Barreras para hebras ■ Una barrera es un mecanismo de sincronización detener (bloquear) la ejecución de un grupo de hebras en un mismo punto del programa ■ Cuando la barrera está a bajo las hebras se detienen en ella en el orden en que llegan ■ Cuando la barrera se levanta, las hebras continúan su ejecución ■ Las barreras son útiles para implementar puntos de sincronización globales en un algoritmo ■ Usamos mutex y variables de condición para implementar barreras ■ Implementamos las siguientes funciones 1. barrier_init() 2. barrier_wait() 3. barrier_destroy() Fernando R. Rannou Diinf-USACH 26 / 30 13 Implementación barreras con pthreads ■ Una posible forma de implementación es usando dos mutexes y dos variables de condición ■ Cada par representa una barrera struct _sb { pthread_cond_t cond; pthread_mutex_t mutex; int runners; }; typedef struct { int maxcnt; struct _sb sb[2]; struct _sb *sbp; } barrier_t; Fernando R. Rannou Diinf-USACH 27 / 30 Inicialización de barrera int barrier_init(barrier_t *bp, int count) { int i; if (count < 1) { printf("Error: numero de hebras debe ser mayor que 1\n"); exit(-1); } bp->maxcnt = count; bp->sbp = &bp->sb[0]; for (i=0; i < 2; i++) { struct _sb *sbp = &(bp->sb[i]); sbp->runners = count; pthread_mutex_init(&sbp->mutex, NULL); pthread_cond_init(&sbp->cond, NULL); } return(0); } Fernando R. Rannou Diinf-USACH 28 / 30 14 Espera con barreras int barrier_wait(barrier_t *bp) { struct _sb *sbp = bp->sbp; pthread_mutex_lock(&sbp->mutex); if (sbp->runners == 1) { if (bp->maxcnt != 1) { sbp->runners = bp->maxcnt; bp->sbp = (bp->sbp = &bp->sb[0])? &bp->sb[1] : &bp->sb[0]; pthread_cond_broadcast(&sbp->cond); } } else { sbp->runners--; while (sbp->runners != bp->maxcnt) pthread_cond_wait(&sbp->cond, &sbp->mutex); } pthread_mutex_unlock(&sbp->wait_mutex); } Fernando R. Rannou Diinf-USACH 29 / 30 Uso de barreras void *proceso(void *arg) { int *tidptr, tid; tidptr = (int *) arg; tid = *tidptr; printf("hola justo antes de la barrera%d\n", tid); barrier_wait(&barrera); } int main(int argc, char *argv[]) { int i, status; pthread_t hebra[10]; barrier_init(&barrera, 10); for (i=0; i < 10; i++){ taskids[i] = (int *) malloc(sizeof(int)); *taskids[i] = i; pthread_create(&hebra[i], NULL, &proceso, taskids[i]); } for (i=0; i < 10; i++) pthread_join(hebra[i], (void **) &status); barrier_destroy(&barrera); } Fernando R. Rannou Diinf-USACH 30 / 30 15