Tema 3: Concurrencia de procesos Yolanda Blanco Fernández
[email protected]
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.
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.
Construcciones FORK y JOIN (Longway) •
Fork
⇒ Divide el flujo en 2 procesos: •
el que viene después de Fork , y
•
el que viene después de la etiqueta .
•
Join : Espera hasta que acaben los contador procesos que debe unir antes de seguir la ejecución.
•
Ejemplo 1: I0
I2
I1
I3
contador = 2; I 0 ; F ORK L1 I 1 ; GOT O L2 ; L1 : I 2 ; L2 : JOIN contador; I 3 ;
Construcciones FORK y JOIN (II) •
Ejemplo 2 : I0
I1
I2
I3
I4
•
•
contador = 3; I 0 ; F ORK L1 ; I 1 ; GOT O L2 ; L1 : F ORK L3 ; I 2 ; GOT O L2 ; L3 : I 3 ; L2 : JOIN contador; I 4 ;
Problemas: •
El uso de GOTO perjudica la legibilidad del programa.
•
No empleado en lenguajes de alto nivel.
•
Difícil depuración ⇒ etiquetas.
Alternativa: Sentencias COBEGIN y COEND .
Construcciones COBEGIN y COEND (Dijkstra) •
También denominadas PARBEGIN y PAREND .
•
COEND cierra tantas ramas como haya abierto COBEGIN .
•
Ejemplo : I 0 ; COBEGIN I 1 , I 2 , . . . , IN ; COEND ; I N +1 ;
I0
I1
I2
IN
IN+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
S
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).
Procesos Concurrentes que comparten Datos •
Al compartir datos entre procesos se pueden producir problemas de indeterminismo (resultados diferentes según escenario de prueba).
•
Ejemplo: S 1 y S 2 no son independientes, sino que comparten la variable x. S0
S2
S1
S3
S 0 : x = 100; S 1 : x := x + 10; S 2 : if x > 100 then write(x); else write (x − 50); S 3 : nop;
•
Escenario #1: S 1 y S 2 ⇒ Se escribe x = 110.
•
Escenario #2 : S 2 y S 1 ⇒ Se escribe x = 50.
•
Escenario #3 : S 2 pierde el control (p. ej. fin de quantum) antes de escribir y sigue S 1 ⇒ Se escribe x = 60.
•
Se han propuesto múltiples soluciones para tratar situaciones indeterministas con procesos concurrentes compartiendo datos.
Solución de Bernstein •
•
Cada proceso P i está asociado a dos conjuntos: •
R(P i ): conjunto de variables accedidas durante la ejecución de P i .
•
W (P i ): conjunto de variables modificadas durante la ejecución de P i .
Bernstein concluyó que para que dos procesos P i y P j , concurrentes, puedan ejecutarse de forma determinista tienen que satisfacerse las siguientes condiciones: •
R(P i ) ∩ W (P j ) = ∅
•
W (P i ) ∩ R(P j ) = ∅
•
W (P i ) ∩ W (P j ) = ∅
•
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.
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.
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 . •
2.
3.
4.
No sirve porque si se pierde el control en libre=FALSE , los dos procesos accederán a la vez al recurso ⇒ indeterminismo!!
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.
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.
La solución válida es el algoritmo de Dekker.
Algoritmo de Dekker
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.
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 .
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.
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 ).
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.
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).
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).
Semáforos Enteros y de Condición •
•
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).
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).
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.
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;
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);
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.
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).
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.
Yolanda Blanco Fernández [email protected] Lab. B-203