Tema 3: Concurrencia de procesos Yolanda Blanco Fernández yolanda@det.uvigo.es Concurrencia, Tiempo Real y Paralelismo • • • • Concurrencia: Convivencia de un conjunto de procesos en un mismo ordenador. Sistemas de tiempo real: Limitados por restricciones temporales críticas. Para vencerlas, los sistemas de tiempo real suelen ser concurrentes. Paralelismo: Posibilidad de ejecutar varias sentencias simultáneamente en un mismo ordenador ⇒ precisan varios procesadores ⇒ concurrencia. Vamos a trabajar con monoprocesadores. Curso 2009/2010 Grafos de Precedencia • Representación gráfica de un algoritmo en el que se establece el orden de ejecución de un conjunto de instrucciones y/o datos. I1 I1 I2 I2 I3 I3 I4 I5 I7 Algoritmo secuencial • • I6 Algoritmo concurrente Los procesos concurrentes de un algoritmo pueden ser independientes o compartir datos. Nuestro objetivo: estudiar ambos tipos de relaciones entre procesos concurrentes, mediante soluciones independientes del procesador y su algoritmo de planificación. Curso 2009/2010 Construcciones FORK y JOIN (Longway) • Fork <etiqueta> ⇒ Divide el flujo en 2 procesos: • el que viene después de Fork, y • el que viene después de la etiqueta. • Join <contador >: Espera hasta que acaben los contador procesos que debe unir antes de seguir la ejecución. Ejemplo 1: • I0 I2 I1 I3 contador = 2; I0 ; F ORK L1 I1 ; GOT O L2 ; L1 : I2 ; L2 : JOIN contador; I3 ; Curso 2009/2010 Construcciones FORK y JOIN (II) • Ejemplo 2: I0 I1 I2 I4 I3 contador = 3; I0 ; F ORK L1 ; I1 ; GOT O L2 ; L1 : F ORK L3 ; I2 ; GOT O L2 ; L3 : I3 ; L2 : JOIN contador; I4 ; • Problemas: • El uso de GOT O perjudica la legibilidad del programa. • No empleado en lenguajes de alto nivel. • Difícil depuración ⇒ etiquetas. • Alternativa: Sentencias COBEGIN y COEND. Curso 2009/2010 Construcciones COBEGIN y COEND (Dijkstra) • • • También denominadas PARBEGIN y PAREND. COEND cierra tantas ramas como haya abierto COBEGIN. Ejemplo: I I0 ; COBEGIN I1 , I2 , . . . , IN ; I I I COEN D; IN +1 ; I 0 1 2 N N+1 • Ejercicio #1: Implementar el siguiente grafo con: (i) sentencias FORK y JOIN y (ii) sentencias COBEGIN y COEND. S1 S2 S3 S4 S5 S6 S7 Curso 2009/2010 Construcciones COBEGIN y COEND (II) • • Fácil incorporación en lenguajes de alto nivel. Depuración más sencilla que FORK y JOIN. ¿Equivalencia entre FORK-JOIN y COBEGIN-COEND? • Ejercicio #2: Escribir el siguiente grafo con sentencias FORK-JOIN y COBEGIN-COEND. S1 S2 S3 S4 S5 S6 S7 • NO son equivalentes. • En UNIX: fork(), execl(), wait() y waitpid(PID). Curso 2009/2010 Procesos Concurrentes que comparten Datos • • Al compartir datos entre procesos se pueden producir problemas de indeterminismo (resultados diferentes según escenario de prueba). Ejemplo: S1 y S2 no son independientes, sino que comparten la variable x. S0 S2 S1 S3 S0 : x = 100; S1 : x := x + 10; S2 : if x > 100 then write(x); else write (x − 50); S3 : nop; • • • Escenario #1: S1 y S2 ⇒ Se escribe x = 110. Escenario #2: S2 y S1 ⇒ Se escribe x = 50. Escenario #3: S2 pierde el control (p. ej. fin de quantum) antes de escribir y sigue S1 ⇒ Se escribe x = 60. • Se han propuesto múltiples soluciones para tratar situaciones indeterministas con procesos concurrentes compartiendo datos. Curso 2009/2010 Solución de Bernstein • Cada proceso Pi está asociado a dos conjuntos: • R(Pi ): conjunto de variables accedidas durante la ejecución de Pi . • W (Pi ): conjunto de variables modificadas durante la ejecución de Pi . • Bernstein concluyó que para que dos procesos Pi y Pj , concurrentes, puedan ejecutarse de forma determinista tienen que satisfacerse las siguientes condiciones: • R(Pi ) ∩ W (Pj ) = ∅ • W (Pi ) ∩ R(Pj ) = ∅ • W (Pi ) ∩ W (Pj ) = ∅ • Condiciones suficientes pero no necesarias ⇒ sólo se pueden compartir variables de lectura. • Objetivo: Buscar soluciones al indeterminismo sin que se cumplan las condiciones de Bernstein ⇒ compartir variables de escritura. Curso 2009/2010 Sección Crítica • • • • • • Ejemplo de motivación: contar el número de coches que pasan por los dos carriles de una autopista. El problema surge cuando los procesos-contadores intentan acceder a una variable compartida. Sección crítica: zona de código de un proceso en la cual se lee y/o modifican las variables compartidas por los procesos concurrentes. Solución: exclusión mutua ⇒ cuando un proceso esté en su sección crítica, ningún otro puede estar en la suya. Para ello, adoptaremos las restricciones de Djkstra: • La solución debe ser independiente del HW o del número de procesos. • No se puede realizar ninguna suposición sobre la velocidad relativa de los procesos. • Cuando un proceso está fuera de su sección crítica no puede impedir a otros procesos entrar en sus respectivas secciones críticas. • La selección del proceso que debe entrar en la sección crítica no puede posponerse indefinidamente. Puede producirse interbloqueo: procesos bloqueados que sólo podrían ser desbloqueados por otros que también están en bloqueo. Curso 2009/2010 Posibles Soluciones al Contador de Coches 1. Asociar una variable booleana libre al acceso al recurso común (la sección crítica). El proceso accede si libre=TRUE. • No sirve porque si se pierde el control en libre=FALSE, los dos procesos accederán a la vez al recurso ⇒ indeterminismo!! 2. Un proceso pasa cuando libre=TRUE y el otro cuando libre=FALSE. • Sólo funciona si la velocidad de ambos procesos está acompasada: si pasan dos coches por el carril izquierdo, sólo se podrá contar el segundo cuando pase un coche por el carril derecho (y ponga libre=TRUE). • Solución inválida por violar la tercera restricción de Djkstra: un proceso impide que otro acceda a la sección crítica cuando no la está usando. 3. Si el segundo proceso quiere acceder a la sección crítica, le dejamos; en caso contrario, accede el primer proceso. • Se garantiza exclusión mutua. • Es posible que los dos procesos se den el turno el uno al otro y ninguno acceda al recurso común ⇒ se viola 4a restricción Djkstra. 4. La solución válida es el algoritmo de Dekker. Curso 2009/2010 Algoritmo de Dekker Curso 2009/2010 Conclusiones • • • • Las soluciones son mucho más complejas de lo que parecen a simple vista. Incluso en el algoritmo de Dekker la sincronización se consigue siempre mediante espera activa (esperar por una condición comprobándola continuamente) ⇒ despilfarro de recursos. La programación concurrente tiene que ser sistemática ⇒ Demasiados escenarios de prueba para ingenieros SW. Es necesario recurrir a herramientas de programación más potentes ⇒ herramientas de sincronización: • Herramientas de bajo nivel. • Herramientas de nivel intermedio: semáforos. • Herramientas de alto nivel: regiones críticas y monitores. Curso 2009/2010 Herramientas de Sincronización • Funciones primitivas, implementadas de forma SW o HW, que ayudan a controlar la interacción entre procesos concurrentes: • Sincronización: Los procesos intercambian señales que controlan su avance. • Comunicación: Los procesos intercambian información. • Características deseables: • Desde el punto de vista conceptual: • Simplicidad. • Generalidad. • Verificabilidad. • Desde el punto de vista de implementación: • Eficiencia. • Tipos de herramientas de sincronización: • Nivel HW: acceso a recursos HW compartidos asegurando uso en exclusión mutua (por ejemplo, memoria y buses). • Nivel SW: LOCK/UNLOCK, TEST-AND-SET, SWAP. Curso 2009/2010 Primitivas LOCK/UNLOCK, TEST-AND-SET y SWAP • • • • • Deben ejecutarse de forma indivisible. LOCK bloquea el acceso a la variable común y UNLOCK lo desbloquea. TEST-AND-SET devuelve el valor de la variable común y la pone a TRUE (para bloquearla). SWAP (var a, b: BOOLEAN) intercambia el valor de a por b y de b por a. Conclusiones: • Consiguen soluciones más sencillas que la propuesta por Dekker. • Solución generalizable a N procesos. • Principal inconveniente: No se elimina la espera activa. • Las herramientas de sincronización de bajo nivel no se usan en aplicaciones concurrentes. • Son la base para las herramientas de alto nivel. Curso 2009/2010 Los Semáforos • • Objetivo: solución al problema de la exclusión mutua evitando la espera activa (Dijkstra, 1965). Un semáforo sem consta de tres partes: • Una variable entera interna (s) con un valor máximo N (no accesible para los procesos). • Una cola de procesos (no accesible para los procesos). • Dos funciones básicas de acceso: • wait(sem): si s < 0, el proceso se suspende en la cola asociada al semáforo. • signal(sem): si hay algún proceso suspendido en la cola asociada al semáforo, se despierta al más prioritario; en caso contrario, se incrementa en una unidad el valor de s (sin superar el valor máximo N ). Curso 2009/2010 Implementación de WAIT y SIGNAL • WAIT(s): s := s − 1 if s < 0 then begin estado-proceso = espera; poner-proceso-en-cola_espera_semáforo; end; • SIGNAL(s): s := s + 1 if s ≤ 0 then begin poner-proceso-en-cola_preparados; estado-proceso = activo; end; • • Si s ≤ 0 ⇒ se bloquean todos los procesos; si s ≥ 1 ⇒ no exclusión mutua. El valor inicial y máximo de la variable s determinan la funcionalidad del semáforo. Curso 2009/2010 Semáforos de Exclusión Mutua • • Semáforos binarios: la variable interna s sólo puede tomar los valores 0 y 1. Solución al problema de la exclusión mutua: • Un semáforo binario con s = 1. • Antes de acceder a la sección crítica el proceso ejecuta wait(sem). • Al finalizar la sección crítica el proceso ejecuta signal(sem). Curso 2009/2010 Semáforos de Paso • Permiten implementar grafos de precedencia. • Para cada punto de sincronización entre dos procesos: semáforo binario con s = 0. • El proceso que debe esperar ejecuta wait(sem). • Al alcanzar un punto de sincronización el otro proceso ejecuta signal(sem). Curso 2009/2010 Semáforos Enteros y de Condición procesos accediendo a la sección crítica: Semáforo con s = N , siendo N el valor máximo permitido. Antes de acceder a la sección crítica el proceso ejecuta wait(sem). Al finalizar la sección crítica el proceso ejecuta signal(sem). • N • • • • Sincronización entre procesos en función de una variable entera: • Semáforo cuya variable s está inicializada a N ó 0 (siendo N el valor máximo). • El proceso que debe esperar ejecuta wait(sem). • Al alcanzar un punto de sincronización el otro proceso ejecuta signal(sem). • Ejemplo clásico: problema del productor-consumidor (con búfer limitado e ilimitado). Curso 2009/2010 Problema del Producto-Consumidor • • • • Sección crítica del productor (P) y del consumidor (C): acceso al búfer. Condiciones: P produce si búfer no lleno y C consume si búfer no vacío. Solución con búfer ilimitado (prob [búfer lleno] ≈ 0) ⇒ El consumidor sólo accederá al búfer cuando haya algún dato que consumir. Solución con búfer limitado ⇒ El productor sólo volcará dato en búfer si hay sitio y consumidor sólo accederá si hay algún dato que consumir. Curso 2009/2010 Solución del Producto-Consumidor con Búfer Ilimitado PROGRAM P-C; VAR buffer: ARRAY [N] of datos; s: SEMAFORO //en exclusión mutua vacio: SEMAFORO //de condición BEGIN s=1; vacio=0; COBEGIN P; C; COEND END PROCEDURE P; BEGIN REPEAT Producir_Dato; WAIT(s); Dejar_Dato_en_Buffer; SIGNAL(s); SIGNAL(vacio); FOREVER; END; PROCEDURE C; BEGIN REPEAT WAIT(vacio); WAIT(s); Extraer_Dato_del_Buffer; SIGNAL(s); Consumir_Dato; FOREVER; END; Curso 2009/2010 Solución del Producto-Consumidor con Búfer Limitado (tamaño N) PROGRAM P-C; VAR buffer: ARRAY [N] of datos; s: SEMAFORO //en exclusión mutua vacio, lleno: SEMAFORO //de condición BEGIN s=1; // exclusión mutua vacio=0; //de condición lleno=N; //de condición COBEGIN P; C; COEND END PROCEDURE P; BEGIN REPEAT Producir_Dato; WAIT(lleno); WAIT(s); Dejar_Dato_en_Buffer; SIGNAL(s); SIGNAL(vacio); FOREVER; END; PROCEDURE C; BEGIN REPEAT WAIT(vacio); WAIT(s); Extraer_Dato_del_Buffer; SIGNAL(s); SIGNAL(lleno); Consumir_Dato; Curso 2009/2010 Problema de la Cena de los Filósofos Arroz • • • Los filósofos cogen 2 palillos, se echan arroz del plato, comen y dejan los palillos. Suponemos que el arroz no se acaba nunca y que cada filósofo sólo puede coger los palillos de los compañeros que tiene a ambos lados. Recurso compartido: los palillos. Curso 2009/2010 Solución ¿válida? al Problema de los Filósofos PROGRAM CENA-FILOSOFOS; VAR palillos: ARRAY [0:4] of SEMAFORO; BEGIN for i=0:4 palillos[i]=1; // exclusión mutua COBEGIN for i=0:4 FILOSOFO[i]; COEND END PROCEDURE FILOSOFO[i]; BEGIN REPEAT Pensar; WAIT(palillos[i]); WAIT(palillos[i+1 mod 5]); Servirse_y_comer; SIGNAL(palillos[i]); SIGNAL(palillos[i+1 mod 5]); FOREVER; END; • Un bloque de varios WAIT no es indivisible; un solo WAIT sí lo es. • Solución inválida: 5 filósofos pueden bloquear un solo palillo y ¡nadie come! • Solución correcta: • “comedor virtual” con 4 comensales (1 filósofo siempre come). • Semáforos utilizados: en exclusión mutua (palillos, incializados a 1) y de condición (comedor, inicializado a 4). Curso 2009/2010 Conclusiones • Ventajas: • Mecanismo seguro de acceso a recurso compartido mediante encapsulamiento de las operaciones sobre la variable que controla el semáforo. • Consiguen sincronización de procesos concurrentes evitando la espera activa. • El programa principal debe inicializar el semáforo (según su uso). • Inconvenientes: • La inicialización es crítica, así como confundir un wait con un signal u omitir alguno de ellos. • Resultan programas muy grandes y complejos, difíciles de depurar. • Soluciones alternativas: regiones críticas y monitores. Curso 2009/2010 Yolanda Blanco Fernández yolanda@det.uvigo.es Lab. B-203 Curso 2009/2010