640 likes | 974 Views
Concurrencia: Exclusión Mútua y Sincronización. Capítulo 5. Concurrencia. Concurrencia abarca conceptos tales como Comunicación entre procesos Compartición y competición de recursos compartidos Sincronización de actividades de multiples procesos colaborativos
E N D
Concurrencia • Concurrencia abarca conceptos tales como • Comunicación entre procesos • Compartición y competición de recursos compartidos • Sincronización de actividades de multiples procesos colaborativos • Asignación de la CPU a los procesos • Concurrencia no sólo surge en sistemas multiprocesadores o sistemas distribuidos, sino también en sistemas monoprocesadores con multiprogramación
Principios de concurrencia • No se puede asumir la velocidad relativa y el orden de ejecución de los procesos, la cual depende de • Las actividades de otros procesos o hebras • El manejo de interrupciones • La política de scheduling del SO • Surgen problemas como • Compartición de recursos globales • Administración eficiente de recursos. ¿Qué pasaría si el SO asigna un recurso de I/O a un proceso y éste inmediatamente se suspende? • Difícil encontrar errores de programación. Los errores de sincronización y concurrencia son típicamente poco determinísticos.
Un ejemplo simple void echo() { chin = getchar(); // asuma que chin is compartida chout = chin; putchar(chout); } Proceso P1Proceso P2 . . chin = getchar(); . . interrupciónchin = getchar(); chout = chin;chout = chin; putchar(chout); . interrupción . putchar(chout); . . • Solución: Permitir que sólo un proceso ejecute la función a la vez
Condición de carrera (race condition) • Una condición de carrera ocurre cuando varios procesos o hebras leen y escriben datos compartidos y el resultado final depende del orden en que se ejecuten las instrucciones • Ejemplo: Inicialmente, b=1, c=2, variables compartidas P1 P2 b = b + c c = b + c
Interacción entre Procesos • Procesos no tienen conocimiento de la existencia de otros procesos • Aunque los procesos no trabajen colaborativamente, el SO debe administrar la competencia por recursos del sistema • Procesos conocen indirectamente la existencia de otros • Saben que los recursos que accesan son compartidos con otros procesos • Procesos que directamente conocen la existencia de otros procesos • Conocen el pid de los otros procesos • Se comunican explícitamente • Cooperan en alguna tarea
Cocurrencia en competencia de recursos • Dos o más procesos necesitan accesar un (o más) recurso durante sus ejecuciones. • Los procesos no saben de la existencia de otros procesos y sus operaciones no deben ser afectadas por la ejecución de otros procesos. • Entonces, los procesos no deben afectar el estado de los recursos. • Problemas potenciales de control • Exclusion mútua - sectiones críticas • Sólo un programa a la vez puede ejecutar su sección crítica • Deadlock (abrazo mortal) • P1 solicita y obtiene recurso R1; P2 solicita y obtiene R2. Luego P1 solicita R2, y P2 solicita R1 • Starvation (inhanición) • Es posible que un proceso esperando por un recurso (bloqueado) nunca lo obtenga
Concurrencia en compartición • Ejemplo: Múltiples procesos acceden variables compartidas • Datos compartidos pueden ser leídos o escritos, y las operaciones de escritura debieran ser exclusivas • Otro problema coherencia de datos • Ejemplo: suponga que necesitamos mantener a=b P1 P2 a = a+1 b = 2*b b = b+1 a = 2*a
Concurrencia en cooperación mediante comunicación • Varios procesos trabajan en conjunto para resolver alguna tarea • Los procesos se comunican explícitamente, por ejemplo mediante el envío de mensajes • Si toda la comunicación está basada en paso de mensajes, no existe problema de exclusión mútua. • Sin embargo, puede existir deadlock e inhanición
Exclusión mútua • Una visión abstracta de la implementación de EM sería P1 P2 void P1() void P2() { { while (true) { while (true) { código antes de la SC código antes de la SC entercritical(Ra); entercritical(Ra); SC; SC; exitcritical(Ra); exitcritical(Ra); código después de la SC código después de la SC } } } }
Requirimientos para exclusión mútua • Sólo un proceso puede estar ejecutando su SC • Un proceso que no se encuentra en su SC no debe interferir con otros procesos que deseen ingresar a la SC • No se permite que un proceso que desea entrar en su SC espere indefinidamente: No deadlock o inhanición • Cuando ningún proceso está en su SC, la solicitud de cualquier proceso para ingresar a su SC no debe ser denegada • No se puede asumir nada respecto de la velocidad relativa y orden en la ejecución de los procesos • Un proceso permanece en su SC por un tiempo finito
Exclusión mútua:solución por hardware • Deshabilitación de Interrupciones • Recordar que un proceso se ejecuta hasta que invoca un llamado al SO o es interrumpido • Entoces para garantizar EM, el proceso deshabilita las interrupciones justo antes de entrar en su SC while (true) { deshabilitar_interrupciones(); SC habilitar_interrupciones(); } • Problema 1: habilidad del procesador para hacer context-switch queda limitada • Problema 2: no sirve para multiprocesadores
EM: solución por hardware • Instrucciones especiales de máquina • 2 acciones se llevan a cabo en forma atómica, como leer y escribir o leer y probar sobre una dirección simple de memoria • Acceso es bloqueado para cualquier otra instrucción • Test and Set Instruction boolean testset (int i) { if (i == 0) { i = 1; return true; } else { return false; } }
EM: solución por hardware • Instrucción Exchange (swap) void exchange(int register, int memory) { int temp; temp = memory; memory = register; register = temp; }
EM: ejemplo • parbegin(P(1), P(2),…,P(n)) suspende la ejecución del programa principal e inicia la ejecución concurrente de n procesos con el mismo código, pero diferente parámetro i • Sólo aquel que encuentra bolt=0 entra. Los otros permanecen en busy-waiting o spin-waiting
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 consume tiempo de procesador • Es posible inhanición • Es posible deadlock si existe prioridades entre los procesos: Suponga dos procesos P1 y P2, con P1 prioridad más baja que P2. Si P1 está en su SC y luego es interrumpido para que P2 entre al procesador, ¿Qué pasaría?
Soluciones por Software • Dos procesos, P0 y P1 comparten la variable global turn la cual indica cuál de los procesos puede ingresar a la seción crítica • Inicialmente turn = 0 • Garantiza exclusión mútua • Problemas • Alternación estricta de ejecución. • ¿Qué pasaría se P0 falla? • No existe inanición P0 P1 while (true) { while (turn != 0); sc(); turn = 1; otro_codigo… } while (true) { while (turn != 1); sc(); turn = 0; otro_codigo… }
Segundo intento • El problema con la solución anterior es que almacenamos el nombre del proceso que puede entrar a la SC • Una nueva posible solución es almacenar el nombre del procesos que está en su SC • Boolean flag[2] • Incialmente flag[0] = flag[1] = false • Si el uno de los procesos falla fuera de su SC, el otro no se bloquea indefinidamente, pero si se bloque dentro de su SC, si. • Esta solución no satisface el requerimiento de exclusión mútua!!! while (true) { while (flag[1]); flag[0] = true; sc(); flag[0] = false; otro_codigo… } while (true) { while (flag[0]); flag[1] = true; sc(); flag[1] = false; otro_codigo… }
Tercer intento • El problema más evidente de la solución anterior es que un proceso puede cambiar su estado después que el otro proceso la ha “consultado”, pero antes que el otro haya entrado a su SC • flag[0] = flag[1] = False • El requerimiento de exclusión mútua es satisfecho, pero… • Se puede producir deadlock ¿por qué? while (true) { flag[1] = true; while (flag[0]); sc(); flag[1] = false; otro_codigo… } while (true) { flag[0] = true; while (flag[1]); sc(); flag[0] = false; otro_codigo… }
Cuarta solución while (true) { flag[1] = true; while (flag[0]){ flag[1] = false; flag[1] = true; } sc(); flag[1] = false; otro_codigo… } while (true) { flag[0] = true; while (flag[1]){ flag[0] = false; flag[0] = true; } sc(); flag[0] = false; otro_código… } • Considere la siguiente secuencia • P0 pone flag[0] en true • P1 pone flag[1] en true • P0 chequea flag[1] • P1 chequea flag[0] • P0 pone flag[0] en false • P1 pone flag[1] en false • P0 pone flag[0] en true • P1 pone flag[1] en true Livelock
Una solución correcta (algoritmo de Peterson) • En este algoritmo usamos dos variables globales: • Turn que indica cual proceso podria entrar a la SC y • flag[] que indica el desea de entrar a la SC • Inicialmente flag[0] = flag[1] = false • Esta solución garantiza exclusión mútua • No produce deadlock while (true) { flag[1] = true; turn = 0; while (flag[0] && turn == 0); sc(); flag[1] = false; otro_codigo… } while (true) { flag[0] = true; turn = 1; while (flag[1] && turn == 1); sc(); flag[0] = false; otro_codigo… }
Semáforos • Una semáforo es una variable especial usada para que dos o más procesos se señalicen mutuamente • Un semáforo es una variable entera • Inicializada con un número no negativo • Sólo dos operaciones acceden al semáforo • semWait(s) decrementa el semáforo; si el valor resultante es negativo, el proceso se bloquea; sino, continúa su ejecución • semSignal(s) incrementa el semáforo; si el valor resultante es menor o igual que zero, entonces se despierta un proceso que fue bloqueado por semWait()
semWait(s) { if (s.value == 1) s.value = 0; else { place this process in s.queue block this process } } semSignal(s) { if (s.queue is empty()) s.value = 1 else { remove a process P from s.queue; place P on the ready list; } Semáforo binario • un semáforo binario puede ser inicializado en 0 ó 1 • también son conocidos como mutex
Políticas de encolamiento define quién, entre aquellos que están esperando, pasa a ejecutarse. semáforo fuerte Garantiza libre de inhanición Ej: FIFO Semáforo débil No garantiza libre de inhanición Ej: Prioridad
El problema del Productor/Consumidor • Uno o más productores generan datos y lo colocan en un buffer (compartido) • Un consumidor toma (consume) los datos del buffer uno a la vez • Sólo un agente (productor o consumidor) accesa el buffer a la vez
Caso buffer infinito producer: while (true) { /* produce item v */ b[in] = v; in++; } consumer: while (true) { while (in <= out) /*do nothing */; w = b[out]; out++; /* consume item w */ }
Caso buffer circular o finito producer: while (true) { /* produce item v */ 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 item w */ }
Semáforo binario s es usado para • asegurar exclusión mútua • Semáforo delay se usa para hacer • que el consumidor espere si el • buffer está vacio • Variable n = in-out Solución incorrecta
Solución caso buffer finito con semáforo contador Semáforo e usado para contar el número de espacios vacíos Bloquearse Productor desea insertar en buffer lleno Consumidor desea sacar de buffer vacío Desbloquearse Consumidor: item insertado Productor: item sacado
semWait(s) { while (!testset(s.flag)) ; s.count--; if (s.count < 0) { place this process in s.queue block this process and set s.flag to 0 } else (NOOOOO) s.flag = 0; semSignal(s) { while (!testset(s.flag)); s.count++; if (s.count <= 0) { remove a process P from s.queue; place P on ready list; } s.flag = 0; Implementación de semáforos con testset() El SO debe garantizar que la operaciones sobre semáforos se realicen en forma atómica En este caso la deshabilitación de interrupciones sería una opción válida
El problema de los filósofos comensales • Cinco filósofos se sientan alrededor de una mesa con una fuente de spaghetti. • Cuando un filósofo no está comiendo, está filosofando. • Como carecen de habilidades manuales, necesitan de dos tenedores para comer, pero sólo hay cinco. • Cuando uno o dos de los tenedores están siendo ocupados por el filósofo de al lado, el filósofo vuelve a filosofar. • Cuando un filósofo deja de comer spaghetti, coloca los tenedores sobre la mesa.
Solución 1 Semaphore fork[5] = {1} void philosopher(int i) { while (true) { think(); semWait(fork[i]); semWait(fork[(i+1)mod 5]); eat(); semSignal(fork[(i+1] mod 5); semSignal(fork[i]); } } main() { parbegin(philosopher(0),philosopher(1), philosopher(2),philosopher(3), philosopher(4)); } • Esta solución no está libre de deadlock
Solución 2 • Una posible solución al problema de deadlock es controlar que sólo 4 tenedores puedan ser usados Semaphore fork[5] = {1} Semaphore room = 4; void philosopher(int i) { while (true) { think(); semWait(room); semWait(fork[i]); semWait(fork[(i+1)mod 5]); eat(); semSignal(fork[(i+1] mod 5); semSignal(fork[i]); semSignal(room); } } main() { parbegin(philosopher(0),philosopher(1), philosopher(2),philosopher(3), philosopher(4)); }
El problema de los lectores/escritores • Uno o más lectores pueden estar simultáneamente leyendo un archivo • Pero sólo un escritor puede estar escribiendo el archivo • Si un escritor está escribiendo el archivo, ningún lector puede estar leyendo el archivo • Ej: sistema de biblioteca • Note que: se garantiza que los lectores sólo leen • Si lectores y escritores leyeran y escribieran, volveriamos al problema general de exclusión mútua
Lectores tienen Prioridad • Escritores puede entrar en inanición
Escritores tienen prioridad, Escritores tienen prioridad, es decir una vez que un escritor declara su deseo de entrar, no se permite que ningún nuevo lector entre
Problema típico con semáforos • ¿Qué pasaría si el programador se equivoca usando las funciones semWait() y semSignal() en el consumidor? n = 0 s = 1 void consumer() { while (true) { semSignal(s); semWait(s); take(); senWait(n); } } void producer() { while (true) { produce(); semWait(s); append(); semSignal(s) senWait(n); } }
Monitores • Un monitor es un módulo de software que evita errores de programación • Carácterísticas principales: • Variables locales son accesadas sólo dentro del monitor; es decir por la funciones del monitor • Para ingresar al monitor, un proceso invoca algunas de las funciones del monitor • Sólo un proceso puede estar ejecutándose en el monitor => exclusión mútua • Los monitores necesitan usar variables de condición: • Suponga que un proceso dentro del monitor necesita suspenderse hasta que una condición se haga verdadera • Las variables de condición proveen el mecanismo de sincronización para suspender un proceso dentro del monitor y reanudar su ejecución en otro momento
Variables de condición • cwait(c): suspende la ejecución del proceso llamador en la variable de condición c. El monitor queda libre para ser usado por otro proceso • csignal(c): resume la ejecución de algún proceso bloqueado por un cwait() sobre la misma variable c. Si hay varios procesos bloqueados, elegir cualquiera; si no hay ninguno bloqueado, no hacer nada • Note que las funciones cwait() y csignal() se parecen a semWait() y semSignal(), PERO NO son iguales y cumplen una función distinta
Solución al problema productor/consumidor con monitor notfull v.c. para dormir el productor cuando buffer está lleno notempty v.c. para dormir al consumidor cuando el buffer está vacío