Taller de Programación Paralela

Documentos relacionados
Sincronización de Hilos POSIX

Concurrencia, exclusión mutua y sincronización. Capítulo 5 HungriaBerbesi

SISTEMAS OPERATIVOS:

Concurrencia: Exclusión mutua y Sincronización

Mensajes. Interbloqueo

Sincronización de Threads

Acceso coordinado a recursos compartidos

Hilos de ejecución POSIX

PROGRAMACIÓN MULTITHREADING

Concurrencia: deberes. Concurrencia: Exclusión Mutua y Sincronización. Concurrencia. Dificultades con la Concurrencia

Teoría de Sistemas Operativos Sincronización Procesos

Sincronización de Procesos. Módulo 6. Departamento de Ciencias e Ingeniería de la Computación Universidad Nacional del Sur. Sincronización de Procesos

Estados de un proceso

Unidad 2: Gestión de Procesos

BENEMERITA UNIVERSIDAD AUTONOMA DE PUEBLA FACULTAD DE CIENCIAS DE LA COMPUTACIÓN LICENCIATURA EN CIENCIAS DE LA COMPUTACIÓN

Lecturas: Ben-Ari, secciones 4.1, 4.2, 4.3, 4.6 Andrews, intro. cap. 4 y sección 4.1. Exclusión mutua con semáforos

Funciones POSIX III Funciones POSIX III. No Nombrados Nombrados. sem_open sem_open. sem_close sem_close. sem_unlink sem_unlink

Concurrencia Monitores. Guillermo Román Díez

Sistemas Operativos. Características de la Multiprogramación. Interacción entre Procesos. Características de la Multiprogramación

Concurrencia. Primitivas IPC con bloqueo

Semáforos. Lecturas: Ben-Ari, secciones 4.1, 4.2, 4.3, 4.6 Andrews, intro. cap. 4 y sección 4.1. Exclusión mutua con semáforos

Introducción a Sistemas Operativos: Concurrencia

UNIDAD 3 MEMORIA COMÚN. El problema de exclusión mutua

Sistemas Operativos Práctica 4

Sistemas operativos. Hasta ahora hemos visto. Relación programa-sistema operativo Gestión de memoria

Concurrencia. Programación Concurrente. Espera ocupada. Primitivas IPC con bloqueo

SISTEMAS OPERATIVOS I (Sistemas) / SISTEMAS OPERATIVOS (Gestión) septiembre 2009

Primitivas de Sincronización (continuación) MONITORES: Primitiva de alto nivel, es a nivel de lenguajes como: ADA, PASCAL, JAVA.

SEMAFOROS. if hay procesos suspendidos en este semáforo then despertar a uno de ellos else S = S + 1

Sistemas Operativos Primer Recuperatorio Parcial 1

Grafo acíclico orientado cuyos nodos corresponden a sentencias individuales.

Sincronización entre procesos (aka: semáforos)

UNIVERSIDAD CARLOS III DE MADRID DEPARTAMENTO DE INFORMÁTICA INGENIERÍA EN INFORMÁTICA. ARQUITECTURA DE COMPUTADORES II 10 junio de 2006

SISTEMAS OPERATIVOS AVANZADOS

Implementación de monitores POSIX

Threads. Hilos - Lightweight process - Procesos ligeros

T5-multithreading. Indice

Lección 6: Ejemplos de programación con semáforos

Sistemas Operativos. Procesos

Concurrencia. Primitivas IPC con bloqueo

Programación concurrente

Sistemas Operativos Ingeniería de telecomunicaciones Sesión 3: Concurrencia

Práctica 8: Barreras

CDI Exclusión mutua a nivel alto. conceptos

Estándares POSIX. Estándares base

PARTE II PROGRAMACION CON THREADS EN C

Capítulo 3: Procesos. n Concepto de Proceso. n Despacho (calendarización) de Procesos. n Operaciones en Procesos. n Procesos en cooperación

*** SOLUCIONES *** SISTEMAS OPERATIVOS Examen Parcial 24 de Abril de 2010

Sistemas Operativos Práctica 3

Test SITR Temas: Planificación, Sincronización, Comunicación entre Procesos, Relojes, Señales, Temporizadores (TestSITR_T4 T9)

SINCRONIZACIÓN DE PROCESOS

SOLUCIONES. Universidad de Las Palmas de Gran Canaria Escuela Universitaria de Informática Facultad de Informática

Tema 3. Monitores Programación Concurrente

Programación concurrente

Datos compartidos: alternativas a deshabilitar interrupciones. Sistemas embebidos para tiempo real

Programación Concurrente y Paralela. P(S) ; sección crítica P(S);

Comunicación y Sincronizacion de procesos. Ingeniería del Software EUITI UPM

Unidad 1: Gestión de Procesos

PROGRAMACIÓN CONCURRENTE

SISTEMAS OPERATIVOS I (Sistemas) / SISTEMAS OPERATIVOS (Gestión) septiembre 2009

Sistemas Operativos (Parte 2)

PROGRAMACIÓN CONCURRENTE. Tema 5 Monitores

Comunicación entre Procesos

PROBLEMAS CLÁSICOS. EL PROBLEMA DE LOS FILÓSOFOS COMENSALES.

Programación Concurrente y Paralela. Unidad 1 Introducción

SISTEMAS OPERATIVOS: PROCESOS. Hilos y Procesos

Guillermo Román Díez

7. Programación Concurrente

Departamento de Lenguajes y Ciencias de la Computación Universidad de Málaga. Sistemas Operativos. Tema 2: Introducción a la programación concurrente

Sistemas Operativos Tema 3. Concurrencia Conceptos generales Sincronización con semáforos

PROGRAMACION CONCURRENTE

Taller de Sistemas Operativos. Sincronización en el núcleo

Si un candado mutex es usado por hilos en el mismo proceso pero no por hilos en diferentes procesos, los primeros dos pasos pueden simplificarse a:

Tema 2. El lenguaje JAVA

Tema 3. Concurrencia entre procesos

Procesos e Hilos en C

Concurrencia en UNIX / LINUX. Introducción: Procesos e Hilos POSIX

- Bajo que condiciones el algoritmo de planifiación de procesos FIFO (FCFS) resultaría en el tiempo de respuesta promedio más pequeño?

Sistemas Complejos en Máquinas Paralelas

dit Programación concurrente Sincronización condicional UPM

Introducción al lenguaje C

Estructuras de Datos Declaraciones Tipos de Datos

Concurrencia. Concurrencia

SOLUCIONES A ALGUNOS DE LOS EJERCICIOS DE SINCRONIZACION Y COMUNICACION ENTRE PROCESOS

Lección 7: Sincronización de procesos mediante monitores

Transcripción:

Taller de Programación Paralela Departamento de Ingeniería Informática Universidad de Santiago de Chile April 4, 2008

y sincronización Cuando dos o más 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 3 / 32

Beneficios de concurrencia Aumento en el rendimiento mediante múltiples procesadores Paralelsimo Aumento del throughput de las aplicaciones Un llamado a una operación de I/O sólo bloquea una hebra Mejora en interactividad de las aplicaciones Asignar mayor prioridad a hebras que atiendan al usuario Mejora en la estructuración de aplicaciones Sistemas distribuidos 4 / 32

Ejemplo de problema 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 Proceso 2 chin = getchar(); chin = getchar(); <- interrupcion chout = chin; chout = chin; <- interrupcion putchar(chout); putchar(chout); Solución: permitir que sólo un proceso ejecute echo() a la vez 5 / 32

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. 6 / 32

* 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 7 / 32

Requerimientos para una solución El objetivo es proveer menanismos que implementen entersc() y exitsc() que satisfagan los siguientes 3 requerimientos Notas 1. Exclusión mútua: Cuando P i está en su SC, ningún otro proceso P j 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 P i que ejecuta entersc(), eventualmente lo haga Un proceso puede tener más de un SC Libre de deadlock no garantiza que algún proceso se bloquee indefinidamente Libre de inhanición no dice cuando un proceso entra a la SC La solución no debe realizar suposiciones sobre la velocidad relativa y orden de ejecución de los procesos 8 / 32

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 9 / 32

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) { int bolts; if (i == 0) { P(i) { i = 1; while (true) { return true; codigo no critico else return false; while (!testset(bolt)); SC(); bolt = 0; codigo no critico void main() { bolt = 0; parbegin(p(1), P(2),..., P(n)); 10 / 32

EM por instrucciones de máquina Ventajas Aplicable a cualquier número de procesos y procesadores que comparten memoria física Simple y fácil de verificar Puede ser usada con varias SC, cada una controlada por su propia variable Desventajas Busy-waiting (o spin-lock) consume tiempo de procesador Es posible inhanición 11 / 32

Soluciones por software 1 Dos procesos comparten la variable turno int turno; P(i) { P(i) { while (true) { while (true) { codigo no critico codigo no critico while (turno!= 0); while (turno!= 1); SC(); SC(); turno = 1; turno = 0; codigo no critico 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 12 / 32

Solución de Peterson Dos procesos comparten dos variable: flag[2] y turno boolean flag[2]; int turno; P(i) { P(i) { while (true) { while (true) { codigo no critico codigo no critico flag[0] = true; flag[1] = true; turno = 1; turno = 0; while (flag[1] && turno == 1); while (flag[0] && turno == 0); SC(); SC(); flag[0] = false; flag[1] = false; codigo no critico codigo no critico void main(){ flag[0] = flag[1] = false; parbegin(p(0), P(1)); 13 / 32

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 14 / 32

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 15 / 32

Semáforo binario o mutex Un semáforo binario sólo puede tomar los valores 0 o 1 wait(s) signal(s) { { if (s.value == 1) if (s.queue is empty()) s.value = 0; s.value = 1 else { else { place this process in s.queue remove a process P from block this process s.queue; place P on the ready list; 16 / 32

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)); 17 / 32

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() { consumer() { while (true) { while (true) { v = produce(); while (in == out); while ((in + 1) % n == out); /* do nothing */ /* do nothing */ w = b[out]; b[in] = v; out = (out + 1) % n; in = (in + 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 18 / 32

Solución productor/consumidor con semáforos const int sizeofbuffer; semaphore s = 1; semaphore full = 0; semaphore empty = sizeofbuffer; producer() consumer() { { while(true) { while(true) { v = produce(); wait(full); wait(empty); wait(s); wait(s); w = take_from_buffer(); put_in_buffer(v); signal(s); signal(s); signal(empty); signal(full); consume(w); 19 / 32

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 20 / 32

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) 21 / 32

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 22 / 32

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; 23 / 32

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); 24 / 32

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); 25 / 32

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() 26 / 32

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; 27 / 32

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); 28 / 32

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); 29 / 32

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); 30 / 32

Semáforos POSIX POSIX define semáforos con nombre y semáforos sin nombre. semáforos con nombre: Para crear un semáforo con nombre invocamos la función: #include <semaphore.h> sem_t *sem_open(const char *name, int oflag, mode_t mode, int value); name: nombre dado al semáforo. oflag: Puede tomar dos valores: O CREAT para crea un semáforo, y O EXCL, en cuyo caso la función falla si el semáforo ya existe. mode t: Setea los permisos sobre el semáforo. value: Especifica el valor inicial del semáforo. Las funciones para incrementar y decrementar el semáforo son: int sem_post(sem_t *sem); int sem_wait(sem_t *sem); Finalmente, para cerrar y eliminar un semáforo: int sem_close(sem_t *sem); int sem_unlink(const char *name); 31 / 32

Semáforos POSIX (cont) semáforos sin nombre: Para crear un semáforo sin nombre invocamos la función: #include <semaphore.h> int sem_init(sem_t *sem, int pshared, unsigned int value); sem: puntero al semáforo (inicializado por sem init()) pshared: Si pthread es cero, el semáforo debe ser visible (compartido) con todas la hebras del proceso. Si pthread es distinto de cero, el semáforo debe ser ubicado en un área de memoria compartida, de tal forma que se comparta entre procesos. value: valor inicial del semáforo. Para incrementar y decrementar el semáforo, usamos sem wait() y sem post(). Un semáforo sin nombre se destruye con: int sem_destroy(sem_t *sem); 32 / 32

Semáforos POSIX (cont) Los semáforos con nombre nos permiten sincronizar procesos no relacionados; en este caso los procesos deben abrir el semáforo con el mismo nombre. Los semáforos sin nombre nos permiten sincronizar hebras de un mismo procesos y procesos relacionados (por ejemplo padre e hijo). 33 / 32