Está en la página 1de 79

UNIVERSIDAD POLITÉCNICA DE MADRID

ESCUELA UNIVERSITARIA DE INFORMÁTICA


LENGUAJES, PROYECTOS Y SISTEMAS INFORMÁTICOS

Tema 9. PROGRAMACIÓN
CONCURRENTE (I)

PROGRAMACIÓN ORIENTADA A OBJETOS AVANZADA


GRADO EN INGENIERÍA DEL SOFTWARE

© Adolfo Yela Ruiz, Fernando Arroyo Montoro y Luis Fernández Muñoz


OBJETIVOS
Presentar los conceptos fundamentales
del paradigma de la Programación
Concurrente, y cómo utilizar
adecuadamente estos conceptos en el
lenguaje Java.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 1


ÍNDICE
1. El Paradigma de la
Programación Concurrente
2. Arquitecturas de sistemas
concurrentes
3. La Abstracción de la
Programación Concurrente
4. Propiedades de corrección
5. Ejecución de procesos
6. Exclusión mutua

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 2


1. EL PARADIGMA DE LA
PROGRAMACIÓN CONCURRENTE
DEFINICIÓN DE CONCURRENCIA:
• En el ámbito de la programación, la concurrencia es el
acaecimiento de varios sucesos al mismo tiempo.
• Estos sucesos pueden ser acciones que ejecuta un programa con
un cierto fin o pueden ocurrir espontáneamente en su entorno.
• Puesto que, en general, estos sucesos pueden durar un cierto
intervalo de tiempo, la concurrencia es un concepto muy amplio,
que engloba a los siguientes:
- Paralelismo: si los sucesos se producen durante el mismo
intervalo de tiempo.
- Solapamiento: si se producen en intervalos de tiempo
superpuestos.
- Simultaneidad: si se producen en el mismo instante de
tiempo (sólo si los sucesos son instantáneos).
Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 3
1. EL PARADIGMA DE LA
PROGRAMACIÓN CONCURRENTE
PROGRAMACIÓN CONCURRENTE:
• El paradigma de la programación concurrente es un conjunto de
teorías, métodos y herramientas, cuyo objetivo es la construcción y
verificación de programas formados por múltiples actividades que
se ejecutan concurrentemente para llevar a cabo una cierta tarea.
• Se han desarrollado distintos lenguajes para representar
actividades simultáneas y para coordinar su ejecución, tales como
ADA, OCCAM, Concurrent-Pascal, o Java.
• Algunas áreas de aplicación típicas de la programación
concurrente son los sistemas operativos, los sistemas de gestión de
bases de datos y los sistemas en tiempo real, tales como los
sistemas de control de procesos o los sistemas multimedia.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 4


1. EL PARADIGMA DE LA
PROGRAMACIÓN CONCURRENTE
PROGRAMA SECUENCIAL:
• Un programa secuencial es un conjunto de declaraciones de
datos e instrucciones (o acciones) ejecutables escrito en un
lenguaje de programación.
• Estas instrucciones deben ejecutarse, una a continuación de otra,
siguiendo una secuencia determinada por un algoritmo, para
resolver un cierto problema.

PROCESO:
• Se denomina proceso a la ejecución de un programa secuencial en
un sistema informático -se dice que es un hilo (thread) de control-.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 5


1. EL PARADIGMA DE LA
PROGRAMACIÓN CONCURRENTE
PROCESOS CONCURRENTES:
• Se dice que dos procesos son concurrentes si se ejecutan al
mismo tiempo en un sistema. Más formalmente, si la primera
instrucción de uno se ejecuta entre la primera y la última del otro
(o viceversa).
PROGRAMA CONCURRENTE:
• Un programa concurrente es un conjunto de varios programas
secuenciales, cuyos procesos pueden ejecutarse concurrentemente
en un sistema para resolver un cierto problema.
SISTEMA CONCURRENTE:
• Un sistema concurrente es un sistema informático (hardware +
software) en el que es posible ejecutar varios procesos
concurrentemente.
Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 6
1. EL PARADIGMA DE LA
PROGRAMACIÓN CONCURRENTE
RELACIONES ENTRE PROCESOS:
• Las posibles relaciones entre procesos concurrentes pueden
clasificarse en:
- Independencia. No existe ninguna interacción entre los
procesos. Este caso es posible, pero es poco interesante.
- Competencia. Esta situación se produce cuando los procesos
deben compartir recursos comunes del sistema (UCP, memoria,
disco, impresora, ...), por lo que deben competir entre ellos
para adquirirlos.
- Cooperación. Esta relación se presenta cuando los procesos
deben trabajar conjuntamente sobre distintas partes de un
problema para resolverlo, para lo cual necesitan intercambiar
información entre sí.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 7


1. EL PARADIGMA DE LA
PROGRAMACIÓN CONCURRENTE
INTERACCIONES ENTRE PROCESOS (I):
• Para poder cumplir las relaciones existentes entre procesos
concurrentes, es necesario considerar dos clases de actividades:
- Sincronización
- Comunicación

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 8


1. EL PARADIGMA DE LA
PROGRAMACIÓN CONCURRENTE
INTERACCIONES ENTRE PROCESOS (II):
- Sincronización. Es el intercambio entre los procesos de
información acerca del control del flujo de ejecución de sus
instrucciones. Sirve para coordinar el orden de ejecución de las
instrucciones de los procesos, imponiendo restricciones para
que no se ejecuten unas instrucciones antes que otras. Se
pueden distinguir dos tipos de sincronización:
- Sincronización condicional. Un proceso debe esperar a
que se cumpla una cierta condición para poder continuar su
ejecución, la cual será activada por otro proceso.
- Exclusión mutua. Cuando varios procesos compiten por un
recurso común de acceso exclusivo, deben coordinar sus
ejecuciones para evitar el acceso concurrente a dicho
recurso.
Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 9
1. EL PARADIGMA DE LA
PROGRAMACIÓN CONCURRENTE
INTERACCIONES ENTRE PROCESOS (III):
- Comunicación. Es el intercambio entre los procesos de
información acerca de sus datos. Cuando varios procesos
deben cooperar para resolver un problema, habitualmente
unos necesitan disponer de datos obtenidos por otros. En este
caso, es obvia la necesidad de sincronizar los procesos, pues o
bien los que necesitan los datos no pueden continuar
progresando en su ejecución mientras no se disponga de ellos
(sincronización condicional), o bien se debe evitar que unos
procesos modifiquen los datos mientras otros estén
leyéndolos, con el fin de mantener su integridad (exclusión
mutua).

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 10


1. EL PARADIGMA DE LA
PROGRAMACIÓN CONCURRENTE
INTERACCIONES ENTRE PROCESOS (IV):

(se resuelven con)


RELACIONES INTERACCIONES

Independencia

Competencia Sincronización

Cooperación Comunicación

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 11


1. EL PARADIGMA DE LA
PROGRAMACIÓN CONCURRENTE
MODELOS DE CONCURRENCIA (I):
• Se han desarrollado dos modelos diferenciados para llevar a cabo
la sincronización y comunicación de procesos concurrentes:
- El modelo de Variables Compartidas
- El modelo de Paso de Mensajes

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 12


1. EL PARADIGMA DE LA
PROGRAMACIÓN CONCURRENTE
MODELOS DE CONCURRENCIA (II):
- El modelo de Variables Compartidas. Los procesos leen o
escriben en variables de acceso común para sincronizarse y
comunicarse. Es decir, un proceso escribe en una variable, la
cual es leída por otro proceso. Debido a la complejidad que
entraña la aplicación directa de este método, tal y como se ha
demostrado en ciertos problemas clásicos (por ejemplo, la
exclusión mutua), se han desarrollado distintas herramientas
de mayor nivel, como las siguientes:
- Semáforos
- Regiones críticas
- Regiones críticas condicionales
- Monitores

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 13


1. EL PARADIGMA DE LA
PROGRAMACIÓN CONCURRENTE
MODELOS DE CONCURRENCIA (III):
- El modelo de Paso de Mensajes. Los procesos se sincronizan y
comunican mediante el intercambio de mensajes. Es decir, un
proceso envía un mensaje que es recibido por otro proceso. Las
herramientas más importantes de este modelo son:
- Envío asíncrono
- Envío síncrono o cita simple
- Invocación remota o cita extendida

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 14


1. EL PARADIGMA DE LA
PROGRAMACIÓN CONCURRENTE
ESTADOS DE UN PROCESO (I):
• Básicamente, un proceso puede estar en tres estados distintos:
- Preparado. El proceso está dispuesto para ejecutar
instrucciones, a falta únicamente de un procesador físico libre.
Este estado se deriva del hecho de que en un sistema
normalmente hay menos procesadores físicos que procesos en
ejecución.
- Ejecución. El proceso está ejecutando instrucciones en un
determinado procesador.
- Bloqueado. El proceso debe esperar la ocurrencia de un
suceso antes de poder hacer uso de un procesador (por
ejemplo, la terminación de una operación de entrada/salida).

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 15


1. EL PARADIGMA DE LA
PROGRAMACIÓN CONCURRENTE
ESTADOS DE UN PROCESO (II):

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 16


2. ARQUITECTURAS DE
SISTEMAS CONCURRENTES
ARQUITECTURAS FÍSICAS (I):
• Atendiendo al número de procesadores físicos, podemos
distinguir dos grandes clases de arquitecturas hardware de sistemas
concurrentes:
- Sistemas uniprocesador. Básicamente están formados por
una única UCP, una memoria, y un sistema de E/S, conectados
mediante varios buses.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 17


2. ARQUITECTURAS DE
SISTEMAS CONCURRENTES
ARQUITECTURAS FÍSICAS (II):
- Sistemas multiprocesador. Podemos distinguir dos tipos de
sistemas multiprocesadores:
- Sistemas fuertemente acoplados. Formados por varias
UCPs, una o más memorias y un sistema de entrada/salida
comunes para todas las UCPs, unidos mediante varios buses.
Además, algunas UCPs pueden tener memorias locales de
acceso particular.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 18


2. ARQUITECTURAS DE
SISTEMAS CONCURRENTES
ARQUITECTURAS FÍSICAS (III):
- Sistemas débilmente acoplados. También llamados
sistemas distribuidos. Formados por un conjunto de nodos,
que pueden estar dispersos geográficamente. Cada nodo
puede ser un sistema uni o multiprocesador. Los nodos no
comparten memoria común, sino que están unidos por
redes de comunicación. Dependiendo de las distancias entre
nodos, se habla de:
- Sistemas multicomputador. De 1 dm a 10 m.
- Redes de área local. De 1 m. a 10 Km.
- Redes de área extensa. De 1 Km. en adelante.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 19


2. ARQUITECTURAS DE
SISTEMAS CONCURRENTES
ARQUITECTURAS FÍSICAS (IV):

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 20


2. ARQUITECTURAS DE
SISTEMAS CONCURRENTES
ARQUITECTURAS FÍSICAS (V):

(se programan con)


SISTEMAS MODELOS

Uniprocesador Variables
compartidas
Multiprocesador
fuertemente
acoplados Paso de mensajes

Distribuidos
Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 21
2. ARQUITECTURAS DE
SISTEMAS CONCURRENTES
MULTIPROCESAMIENTO Y MULTIPROGRAMACIÓN (I):
• Un programa concurrente puede ejecutarse de forma que cada
proceso se ejecute en su propio procesador (multiprocesamiento)
o que los procesos compartan uno o más procesadores
(multiprogramación).
- Multiprocesamiento. En un sistema multiprocesador, es
posible que cada proceso de un programa concurrente se
ejecute en un procesador físico distinto.
• Este primer enfoque se denomina multiprocesamiento si
los procesadores comparten memoria común (sistemas
multiprocesadores fuertemente acoplados), o como
procesamiento distribuido si los procesadores están
conectados por una red de comunicaciones (sistemas
distribuidos).
Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 22
2. ARQUITECTURAS DE
SISTEMAS CONCURRENTES
MULTIPROCESAMIENTO Y MULTIPROGRAMACIÓN (II):
• En el multiprocesamiento, se dice que existe paralelismo
real en la ejecución de los procesos, lo que permite un
aumento de la velocidad de ejecución del programa.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 23


2. ARQUITECTURAS DE
SISTEMAS CONCURRENTES
MULTIPROCESAMIENTO Y MULTIPROGRAMACIÓN (III):
- Multiprogramación. Sin embargo, el concepto de
programación concurrente admite la ejecución simultánea de
más de un proceso en un único procesador físico.
• Este segundo enfoque se conoce como multiprogramación
y consiste en multiplexar la ejecución de los procesos sobre
los procesadores mediante la intercalación de la ejecución
de sus instrucciones.
• Esto se suele conseguir mediante la técnica conocida como
compartición de tiempo, en la que se asigna una cuota de
tiempo a cada proceso para su ejecución en un procesador.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 24


2. ARQUITECTURAS DE
SISTEMAS CONCURRENTES
MULTIPROCESAMIENTO Y MULTIPROGRAMACIÓN (IV):
• En la multiprogramación, se habla de paralelismo
simulado o de pseudoparalelismo. Excepto en lo que se
refiere a su velocidad de ejecución, la sensación que
proporciona el sistema es similar a la que se obtendría con
paralelismo real.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 25


2. ARQUITECTURAS DE
SISTEMAS CONCURRENTES
MULTIPROCESAMIENTO Y MULTIPROGRAMACIÓN (V):
• También son posibles enfoques híbridos, por ejemplo, los
procesadores de un sistema distribuido pueden multiprogramarse.
• Como sabemos, con el multiprocesamiento es posible aumentar la
velocidad de ejecución de un programa concurrente, mediante las
asignación de diferentes procesadores físicos a distintos procesos.
• Sin embargo, multiprogramar varios procesos concurrentes no
aumenta la velocidad de ejecución del programa. Incluso, puede
disminuir, debido a que la intercalación de sus ejecuciones en el
procesador implica un cierto coste adicional.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 26


2. ARQUITECTURAS DE
SISTEMAS CONCURRENTES
MULTIPROCESAMIENTO Y MULTIPROGRAMACIÓN (VI):
• Las principales razones para usar la multiprogramación de
procesos concurrentes son:
- Aprovechar la posibilidad de ejecutar procesos concurrentes
cuando el dominio del problema no está totalmente ordenado.
En ciertas aplicaciones, el orden de ejecución de ciertas
operaciones del programa no está fijada de antemano, sino
que puede depender de sucesos externos cuyo orden se
desconoce y, por tanto, debe determinarse en tiempo de
ejecución
- Dar un servicio interactivo a varios usuarios
simultáneamente, por ejemplo, los sistemas operativos
multiusuario.
Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 27
2. ARQUITECTURAS DE
SISTEMAS CONCURRENTES
MULTIPROCESAMIENTO Y MULTIPROGRAMACIÓN (VII):
- Mejorar la utilización del procesador. Cuando un proceso
está bloqueado, esperando un cierto suceso (por ejemplo, en
una operación de E/S), es posible asignar el procesador a otro
proceso. Aunque esto pueda ralentizar la ejecución de cada
proceso individual, se aumenta la utilización del procesador y,
por tanto, la velocidad de ejecución del programa global.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 28


2. ARQUITECTURAS DE
SISTEMAS CONCURRENTES
GESTIÓN DE PROCESOS CONCURRENTES (I):
• Las dos actividades principales que son necesarias para organizar
la ejecución de un programa concurrente son la planificación y el
despacho:
- Planificación (scheduling). Consiste en determinar la
asignación, en cada instante, de los procesos existentes a los
procesadores físicos disponibles.
• En general, se debe planificar, es decir, decidir a qué
proceso debe asignarse el procesador, cuando se cumpla
alguna de las siguientes condiciones:
- El proceso que tiene el control de la UCP termina.
- El proceso se bloquea por alguna razón.
- Cuando finaliza su cuota de tiempo.
- Se produce un error en su ejecución.
Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 29
2. ARQUITECTURAS DE
SISTEMAS CONCURRENTES
GESTIÓN DE PROCESOS CONCURRENTES (II):
• Cuando la UCP queda disponible, el algoritmo de
planificación determina a qué proceso preparado se le
asigna, usando diferentes técnicas: el que más tiempo lleve
esperando, por prioridades, aleatorio, etc.
• Hay dos conceptos importantes acerca de la planificación
muy relacionados con la programación concurrente:
- Justicia: Un planificador es justo si a todo proceso
preparado se le asigna en algún momento un
procesador para ejecutarse.
- El comportamiento funcional (datos/resultados) de un
programa concurrente no debe depender del algoritmo
de planificación empleado.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 30


2. ARQUITECTURAS DE
SISTEMAS CONCURRENTES
GESTIÓN DE PROCESOS CONCURRENTES (III):
- Despacho (dispatching). Consiste en entregar el control de la
UCP al proceso elegido por el planificador para su ejecución.
• Cuando un proceso abandona una UCP, se almacena una
copia de los valores de todos los registros de la UCP, para
que puedan restaurarse cuando el proceso sea despachado
de nuevo, como consecuencia de haber sido seleccionado
por el planificador.
• Esto es necesario para conseguir que el proceso reanude
su ejecución exactamente en las mismas condiciones que
cuando la detuvo, sin sufrir ninguna interferencia por parte
de los procesos que hayan visitado la UCP en ese período.
• Esto equivale a suponer que cada proceso dispone de su
propio juego virtual de registros de la UCP.
Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 31
3. LA ABSTRACCIÓN DE LA
PROGRAMACIÓN CONCURRENTE
DEFINICIÓN:
“La abstracción de la programación concurrente es el estudio de
las secuencias de ejecución intercalada de las instrucciones
atómicas de los procesos secuenciales“ (Ben-Ari, 1990).

• Para alcanzar esta abstracción se ignorarán detalles tales como:


1. El número de procesadores físicos disponibles,
2. Las velocidades de ejecución de los procesos,
3. Y los posibles solapamientos en las ejecuciones de las
instrucciones indivisibles de cada proceso.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 32


3. LA ABSTRACCIÓN DE LA
PROGRAMACIÓN CONCURRENTE
INSTRUCCIONES ATÓMICAS (I):
• Una instrucción (o acción) atómica de un proceso es aquélla cuya
ejecución es indivisible en un cierto sistema.
• En consecuencia, ningún otro proceso puede observar los efectos
de una instrucción atómica ni puede interferir en sus resultados
durante su ejecución.
• Sin embargo, no se puede hacer esta suposición sobre lo que
ocurre entre la ejecución de dos acciones atómicas sucesivas.
• Para aclarar el concepto de instrucción atómica, supongamos la
siguiente instrucción de un cierto proceso, que incrementa en una
unidad el valor de una variable entera x:
x = x + 1;

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 33


3. LA ABSTRACCIÓN DE LA
PROGRAMACIÓN CONCURRENTE
INSTRUCCIONES ATÓMICAS (II):
• Esta instrucción, en general, puede no ser atómica, ya que el
código resultante, escrito en un cierto lenguaje ensamblador,
podría ser el siguiente:

a1: LOAD R, X (* Cargar X en el registro R *)


a2: ADD R, #1 (* Sumar 1 a R *)
a3: STORE R, X (* Almacenar R en X *)

• Si estas instrucciones son indivisibles en un cierto procesador,


debido a que existen mecanismos (hardware o software) que
garanticen su ejecución sin interrupción, entonces podremos
asegurar que son atómicas.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 34


3. LA ABSTRACCIÓN DE LA
PROGRAMACIÓN CONCURRENTE
INSTRUCCIONES ATÓMICAS (III):
• Podemos distinguir dos clases de instrucciones atómicas:
- De grano fino. Una instrucción atómica de grano fino es
aquélla que se implanta directamente por el hardware en el
que se ejecuta el programa concurrente. Se corresponden con
las instrucciones máquina de la UCP.
- De grano grueso. Una instrucción atómica de grano grueso
es una secuencia de instrucciones atómicas de grano fino, cuya
ejecución es indivisible. Esto se consigue mediante
herramientas software que permitan sincronizar la ejecución
de los procesos. Estas herramientas pueden ser proporcionadas
por un lenguaje de programación o pueden estar implantadas
en un sistema operativo subyacente.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 35


3. LA ABSTRACCIÓN DE LA
PROGRAMACIÓN CONCURRENTE
INSTRUCCIONES ATÓMICAS (IV):

1ª abstracción: (Ben-Ari, 1990)


“En la programación concurrente se considera que cada proceso se
ejecuta en su propio procesador. Esto permite tener en cuenta
únicamente las interacciones entre los procesos derivadas de sus
relaciones de competencia y cooperación.”

2º abstracción: (Ben-Ari, 1990)


“Una abstracción superior es ignorar las velocidades relativas de
cada proceso, lo que posibilita eliminar la variable tiempo y
considerar sólo las secuencias de instrucciones atómicas que se
ejecutan.”

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 36


3. LA ABSTRACCIÓN DE LA
PROGRAMACIÓN CONCURRENTE
INTERCALACIÓN (I):

3ª abstracción: (Ben-Ari, 1990)


“Se considera que las secuencias de ejecución de las instrucciones
atómicas de todos los procesos se intercalan en una única
secuencia.”

• Esta abstracción considera que la ejecución de dos instrucciones


atómicas sólo puede intercalarse, pero no solaparse.
• Esta restricción se basa en la suposición de que el resultado de la
ejecución simultánea de dos instrucciones atómicas debe ser
cualquiera de los dos posibles resultados obtenidos al ejecutar las
instrucciones secuencialmente.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 37


3. LA ABSTRACCIÓN DE LA
PROGRAMACIÓN CONCURRENTE
INTERCALACIÓN (II):
• Esta suposición es totalmente adecuada en el caso de
multiprogramación, puesto que la ejecución de las instrucciones
atómicas siempre debe intercalarse por la competencia por un
único procesador.
• En multiproceso, dos instrucciones atómicas pueden ejecutarse,
en principio, simultáneamente.
• Sin embargo, si las instrucciones compiten por un cierto recurso
de acceso exclusivo, por ejemplo, tratan de escribir
simultáneamente en una misma posición de memoria, el hardware
o software subyacente debe resolver el problema fijando un cierto
orden arbitrario (porque, por definición, son atómicas).

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 38


3. LA ABSTRACCIÓN DE LA
PROGRAMACIÓN CONCURRENTE
INTERCALACIÓN (IV):
• Si las instrucciones son independientes, su resultado será el
mismo cualquiera que sea el orden en que se ejecuten, por lo que
también podemos suponer un cierto orden arbitrario.
• Más aún, la abstracción de la programación concurrente no
impone una intercalación fija de las instrucciones atómicas de los
procesos, sino que hay que considerar todas las posibles
secuencias de ejecución intercalada de sus instrucciones atómicas.
• Esto es un problema de gran complejidad porque el número de
posibles intercalaciones crece exponencialmente al aumentar el
número de procesos y el número de sus instrucciones atómicas.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 39


3. LA ABSTRACCIÓN DE LA
PROGRAMACIÓN CONCURRENTE
INDETERMINISMO (I):
• Una cualidad intrínseca de la programación concurrente se
denomina indeterminismo, es decir, la imposibilidad de reproducir
exactamente la ejecución de un programa concurrente que parte
del mismo estado inicial de su entorno.
• Como consecuencia, dos ejecuciones del mismo programa con los
mismos datos pueden producir distintos resultados, incluso en el
mismo sistema.
• Esto es debido a que, aun cuando el estado inicial sea el mismo, la
secuencia de ejecución de sus instrucciones atómicas puede ser
diferente, lo que provoca que el programa pase por distintos
estados intermedios.
• Esto no sucede en el caso de un programa secuencial, cuyo
comportamiento es completamente determinista.
Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 40
3. LA ABSTRACCIÓN DE LA
PROGRAMACIÓN CONCURRENTE
INDETERMINISMO (II):
• Consideremos el siguiente programa concurrente, escrito en un
lenguaje de programación abstracto.
int x = 0;
PROCESS P1 : x = x + 1;
PROCESS P2 : x = x + 1;

• Si las instrucciones de incremento no son atómicas, sino que se


descomponen en las siguientes instrucciones atómicas:
PROCESS P1 : x = x + 1; -> a11) LOAD R1, X
a12) ADD R1, #1
a13) STORE R1, X
PROCESS P2 : x = x + 1; -> a21) LOAD R2, X
a22) ADD R2, #1
a23) STORE R2, X

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 41


3. LA ABSTRACCIÓN DE LA
PROGRAMACIÓN CONCURRENTE
INDETERMINISMO (III):
• Entonces hay que considerar 20 posibles secuencias de ejecución
intercalada de las instrucciones atómicas de los procesos, de las
cuales sólo las 2 siguientes producen un resultado final igual a 2 en
la variable x:
a11 -> a12 -> a13 -> a21 -> a22 -> a23  x = 2

a21 -> a22 -> a23 -> a11 -> a12 -> a13  x = 2

• mientras que el resto de secuencias de ejecución producen un


resultado final igual a 1 en la variable x:
a11 -> a21 -> a12 -> a13 -> a22 -> a23  x = 1
a11 -> a21 -> a22 -> a12 -> a13 -> a23  x = 1
...

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 42


3. LA ABSTRACCIÓN DE LA
PROGRAMACIÓN CONCURRENTE
INDETERMINISMO (IV):
• El ejemplo anterior ilustra una propiedad importante, que el
indeterminismo, aumenta cuando disminuye el grano de las
acciones atómicas de cada proceso.
• Esto es debido a que el número de posibles intercalaciones crece
exponencialmente con el número de acciones atómicas de cada
proceso, y con ellas, el número de posibles estados intermedios.
• Un objetivo primordial de la programación concurrente es tratar
de reducir este indeterminismo, sincronizando los procesos para
restringir el número de posibles secuencias de instrucciones de un
programa concurrente sólo a aquéllas deseables.
• Si en el ejemplo anterior sincronizamos los procesos para
garantizar que hay exclusión mutua en el acceso a la variable x,
entonces el resultado final siempre será 2.
Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 43
4. PROPIEDADES DE
CORRECCIÓN
• La corrección de un programa concurrente se define en términos
de las propiedades de sus secuencias de ejecución.
• Una propiedad de un programa concurrente es un atributo que
debe satisfacerse para todas sus posibles secuencias de ejecución.
• Un programa correcto debe cumplir dos clases de propiedades en
toda posible ejecución del mismo:
a) Propiedades de seguridad (safety).
b) Propiedades de vitalidad (liveness).

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 44


4. PROPIEDADES DE
CORRECCIÓN
a) PROPIEDADES DE SEGURIDAD (I):
• Deben ser ciertas en todo instante de la ejecución del programa.
• Aseguran que nada malo ocurrirá nunca durante una ejecución.
• Algunos ejemplos de propiedades de esta clase son:
a.1) Exclusión mutua. Establece que nunca puedan intercalarse
ciertas secuencias de instrucciones de un grupo de procesos. Es
decir, una secuencia debe terminarse de ejecutar antes de que
ninguna otra comience. Esta propiedad sirve para garantizar el
acceso exclusivo a recursos compartidos por los procesos.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 45


4. PROPIEDADES DE
CORRECCIÓN
a) PROPIEDADES DE SEGURIDAD (II):
a.2) Ausencia de interbloqueo pasivo (deadlock). Un
interbloqueo pasivo es un estado en el que un conjunto de
procesos se bloquean esperando un suceso que sólo puede
producir otro proceso del propio conjunto. Puesto que todos
esperan, ninguno puede producir el suceso que desbloquearía
a otro y, por tanto, todos continúan esperando
indefinidamente.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 46


4. PROPIEDADES DE
CORRECCIÓN
b) PROPIEDADES DE VITALIDAD:
• Deben ser eventualmente ciertas, es decir, deben cumplirse en
algún instante de la ejecución del programa.
• Aseguran que algo bueno ocurrirá eventualmente durante su
ejecución.
• Algunos ejemplos de propiedades de esta clase son:
b.1) Ausencia de interbloqueo activo (livelock). Un
interbloqueo activo se produce cuando ciertos procesos no se
bloquean, pero ejecutan instrucciones inútiles sin obtener
ningún progreso.
b.2) Ausencia de inanición (starvation). Si un proceso hace
una petición para obtener un recurso, debe garantizarse que
sea atendida eventualmente. Lo que no puede ocurrir es que a
un proceso no se le atienda nunca, mientras otros procesos
obtienen ese recurso repetidamente. Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 47
4. PROPIEDADES DE
CORRECCIÓN
c) PROPIEDADES DE JUSTICIA (fairness):
• Cuando varios procesos compiten por recursos comunes, es
deseable poder especificar cómo debe resolverse esta
competencia.
• Algunos ejemplos de propiedades de esta clase son:
c.1) Ausencia de inanición. Si un proceso hace una petición,
debe garantizarse que sea atendida eventualmente.
c.2) Espera lineal. Si un proceso hace una petición, debe
garantizarse que se le conceda antes de que se le conceda a
otro proceso más de una vez.
c.3) Espera FIFO. Si un proceso hace una petición, debe
concedérsela antes de a cualquier otro proceso que la haga
posteriormente.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 48


5. EJECUCIÓN DE PROCESOS
HILOS (I):
• En Java, los procesos concurrentes se modelan mediante el
concepto de “hilo” (thread).
• La máquina virtual de Java permite que una aplicación ejecute
varios hilos concurrentemente.
• Cuando comienza la ejecución de la máquina virtual,
normalmente hay un único hilo que invoca al método main() de la
clase principal.
• La máquina virtual continúa ejecutando los hilos de la aplicación
hasta que se cumpla alguna de las siguientes condiciones:
• se invoca el método exit() de la clase Runtime,
• todos los hilos de la aplicación terminan su ejecución.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 49


5. EJECUCIÓN DE PROCESOS
HILOS (II):
• En Java, hay dos maneras para crear y ejecutar un nuevo hilo.
• La primera consiste en seguir los siguientes pasos:
• crear una clase que herede de la clase Thread,
• redefinir el método run() de la clase Thread en la subclase,
• crear un objeto de esa subclase,
• e invocar el método start() de la clase Thread sobre el objeto
de la subclase.
CLASE Thread:
public Thread()
public Thread(Runnable target)
public void run()
public void start()
public static void sleep(long millis) throws InterruptedException
public final void join() throws InterruptedException Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 50
5. EJECUCIÓN DE PROCESOS
Ej.
class Proceso extends Thread {

private int id;

public Proceso(int id) {


this.id = id;
}

public void run() {


for (int i = 0; i < 50; i++) {
System.out.print(id);
}
}
}

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 51


5. EJECUCIÓN DE PROCESOS
Ej.
class Aplicacion {

public static void main(String[] args) {


Proceso p1 = new Proceso(1);
Proceso p2 = new Proceso(2);
p1.start();
p2.start();
}
}

• en una ejecución puede producir este resultado:


111111111111111111112122222222222221222121212121211111111111111111111212
1212222222222222222222222222

• en otra ejecución puede producir este otro resultado:


111222222222222222212222222222222222222122222222222222211111111111111111
1111111111111111111111111111

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 52


5. EJECUCIÓN DE PROCESOS
HILOS (III):
• La segunda manera de ejecutar un hilo consiste en seguir los
siguientes pasos:
• crear una clase que implemente el interfaz Runnable,
• redefinir el método run() del interfaz Runnable en la subclase,
• crear un objeto de esa subclase,
• crear un objeto de la clase Thread pasando como argumento
el objeto anterior,
• e invocar el método start() de la clase Thread sobre el objeto
de la clase Thread.
• Esta segunda manera permite ejecutar hilos de clases que
hereden de otra clase que no sea la clase Thread, cosa que no sería
posible usando la primera manera, ya que Java no permite la
herencia múltiple.
Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 53
5. EJECUCIÓN DE PROCESOS
Ej.
class Impresor {

private int id;

public Impresor(int id) {


this.id = id;
}

public void imprimir() {


System.out.print(id);
}
}

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 54


5. EJECUCIÓN DE PROCESOS
Ej.
class Proceso extends Impresor implements Runnable {

public Proceso(int id) {


super(id);
}

public void run() {


for (int i = 0; i < 50; i++) {
this.imprimir();
}
}
}

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 55


5. EJECUCIÓN DE PROCESOS
Ej.
class Aplicacion {

public static void main(String[] args) {


Proceso p1 = new Proceso(1);
Proceso p2 = new Proceso(2);
Thread h1 = new Thread(p1);
Thread h2 = new Thread(p2);
h1.start();
h2.start();
}
}

• en una ejecución puede producir este resultado:


121212211212121212111111111111112122222222222222222122222222222122222222
2222111111111111111111111111

• en otra ejecución puede producir este otro resultado:


111111111111111111111111111111111111111111111111112222222222222222222222
2222222222222222222222222222
Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 56
5. EJECUCIÓN DE PROCESOS
HILOS (IV):
• Para esperar a que un hilo termine su ejecución se debe invocar el
método join() de la clase Thread. En la primera versión quedaría:
class Aplicacion {

public static void main(String[] args) {


Proceso p1 = new Proceso(1);
Proceso p2 = new Proceso(2);
p1.start();
p2.start();
try {
p1.join();
p2.join();
} catch (InterruptedException ex) { ex.printStackTrace(); ...}
System.out.println("\nFin del programa");
}
}

111111111111111111212111111111111111111121212121212222222222121212111122
2222222222222222222222222222
Fin del programa
Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 57
5. EJECUCIÓN DE PROCESOS
HILOS (V):
• En la segunda versión quedaría:
class Aplicacion {

public static void main(String[] args) {


Proceso p1 = new Proceso(1);
Proceso p2 = new Proceso(2);
Thread h1 = new Thread(p1);
Thread h2 = new Thread(p2);
h1.start();
h2.start();
try {
h1.join();
h2.join();
} catch (InterruptedException ex) { ex.printStackTrace(); ...}
System.out.println("\nFin del programa");
}
}

111111111111111111121212121212121212122222222222222222221222222222222222
2222111111111111111111111222
Fin del programa
Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 58
5. EJECUCIÓN DE PROCESOS
Ejemplo:

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 59


5. EJECUCIÓN DE PROCESOS
Ej.
class Aplicacion {

private static Ventana ventana;


public static Ventana getVentana() {
return ventana;
}

public Aplicacion() {
ventana = new Ventana();
}

public static void main(String[] args) {


new Aplicacion();
}
}

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 60


5. EJECUCIÓN DE PROCESOS
import java.awt.Dimension;
import javax.swing.JFrame;

class Ventana extends JFrame {

public Ventana() {
PanelContenido panelContenido = new PanelContenido();
this.setContentPane(panelContenido);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setTitle("Ordenador");
this.setLocation(150, 150);
this.setPreferredSize(new Dimension(500, 150));
this.pack();
this.setResizable(true);
this.setVisible(true);
}
}

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 61


5. EJECUCIÓN DE PROCESOS
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JPanel;
class PanelContenido extends JPanel implements ActionListener {
private JButton iniciar;
private PanelProceso panelBurbuja;
private PanelProceso panelSeleccion;
public PanelContenido() {
iniciar = new JButton("Iniciar");
iniciar.addActionListener(this);
panelBurbuja = new PanelProceso();
panelBurbuja.setBorder(
BorderFactory.createTitledBorder(" Burbuja "));
panelSeleccion = new PanelProceso();
panelSeleccion.setBorder(
BorderFactory.createTitledBorder(" Selección "));

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 62


5. EJECUCIÓN DE PROCESOS
GroupLayout distribuidor = new GroupLayout(this);
this.setLayout(distribuidor);
distribuidor.setAutoCreateGaps(true);
distribuidor.setAutoCreateContainerGaps(true);
distribuidor.setHorizontalGroup(
distribuidor.createParallelGroup(
GroupLayout.Alignment.CENTER)
.addGroup(distribuidor.createSequentialGroup()
.addComponent(panelBurbuja)
.addComponent(panelSeleccion))
.addComponent(iniciar));
distribuidor.setVerticalGroup(
distribuidor.createSequentialGroup()
.addGroup(distribuidor.createParallelGroup(
GroupLayout.Alignment.CENTER)
.addComponent(panelBurbuja)
.addComponent(panelSeleccion))
.addComponent(iniciar));
}

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 63


5. EJECUCIÓN DE PROCESOS
public void actionPerformed(ActionEvent ev) {
iniciar.setEnabled(false);
int[] array = new int[10];
for (int i = 0; i < array.length; i++) {
array[i] = (int) (10.0 * Math.random());
}
final Ordenador burbuja = new Burbuja(array, panelBurbuja);
final Ordenador seleccion = new Seleccion(array, panelSeleccion);
burbuja.start();
seleccion.start();
new Thread() {
public void run() {
try {
burbuja.join();
seleccion.join();
} catch (InterruptedException ex) {
ex.printStackTrace();
System.exit(-1);
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
iniciar.setEnabled(true);
}
});
}
}.start();
}
}
Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 64
5. EJECUCIÓN DE PROCESOS
abstract class Ordenador extends Thread {
protected int[] array;
protected PanelProceso panel;
protected Ordenador(int[] array, PanelProceso panel) {
this.array = new int[array.length];
for (int i = 0; i < array.length; i++) {
this.array[i] = array[i];
}
this.panel = panel;
}
protected abstract void ordenar();

public void run() {


this.ordenar();
}
public void esperar() {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) { ex.printStackTrace(); }
}
}
Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 65
5. EJECUCIÓN DE PROCESOS
class Burbuja extends Ordenador {
protected Burbuja(int[] array, PanelProceso panel) {
super(array, panel);
}
protected void ordenar() {
panel.escribir(array);
this.esperar();
for (int i = 1; i < array.length; i++) {
for (int k = array.length - 1; k >= i; k--) {
if (array[k] < array[k - 1]) {
int aux = array[k];
array[k] = array[k - 1];
array[k - 1] = aux;
panel.escribir(array);
this.esperar();
}
}
}
}
}

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 66


5. EJECUCIÓN DE PROCESOS
class Seleccion extends Ordenador {
protected Seleccion(int[] array, PanelProceso panel) {
super(array, panel);
}
protected void ordenar() {
panel.escribir(array);
this.esperar();
for (int i = 0; i < array.length - 1; i++) {
int min = i;
for (int j = i + 1; j < array.length; j++) {
if (array[j] < array[min]) {
min = j;
}
}
if (i != min) {
int aux = array[i];
array[i] = array[min];
array[min] = aux;
panel.escribir(array);
this.esperar();
}
}
}
} Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 67
5. EJECUCIÓN DE PROCESOS
class PanelProceso extends JPanel {

private JLabel label;

public PanelProceso() {
label = new JLabel("");
this.add(label);
}

public void escribir(int[] array) {


String cadena = "";
for (int i = 0; i < array.length; i++) {
cadena = cadena + array[i] + " ";
}
final String aux = cadena;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
label.setText(aux);
}
});
}
}

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 68


6. EXCLUSIÓN MUTUA
• En Java, al igual que en otros lenguajes, la exclusión mutua sobre
los objetos de un programa concurrente se consigue mediante los
monitores (Hoare, 1974).
• Un monitor es un módulo de un programa concurrente que
encapsula la representación de recursos abstractos, tales como
variables compartidas o dispositivos de entrada y salida.
• Una vez encapsulado el recurso dentro de un monitor, el único
medio para acceder al recurso es invocar una de las operaciones
que el monitor proporciona que, por definición, se realizan bajo el
requisito de exclusión mutua sobre ese recurso.
• Es decir, si mientras un proceso está ejecutando una operación del
monitor, otro proceso trata de ejecutar cualquier operación del
mismo monitor, entonces el segundo proceso se bloquea hasta que
el primero termine de ejecutar su operación sobre el recurso que
representa ese monitor.
Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 69
6. EXCLUSIÓN MUTUA
• Java proporciona la sentencia synchronized para indicar que
ciertas secuencias de instrucciones se deben ejecutar bajo
exclusión mutua sobre un determinado objeto.
SINTAXIS (I):
synchronized(<referencia>) {
<sentencia1>

<sentenciaN>
}

• lo que indica que cualquier secuencia de sentencias que se


encuentre en interior de un bloque sincronizado de un mismo
objeto debe ejecutarse bajo el requisito de exclusión mutua.

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 70


6. EXCLUSIÓN MUTUA
Ej.
class Entero {

private int i;

public Entero(int i) {
this.i = i;
}

public void incrementar() {


i = i + 1;
}

public int get() {


return i;
}
}

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 71


6. EXCLUSIÓN MUTUA
class Proceso extends Thread {

private Entero entero;

public Proceso(Entero entero) {


this.entero = entero;
}

public void run() {


for (int i = 0; i < 5000; i++) {
entero.incrementar();
}
}
}

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 72


6. EXCLUSIÓN MUTUA
class Aplicacion {
public static void main(String[] args) {
Entero entero = new Entero(0);
Proceso[] procesos = new Proceso[10];
for (int i = 0; i < procesos.length; i++) {
procesos[i] = new Proceso(entero);
}
for (int i = 0; i < procesos.length; i++) {
procesos[i].start();
}
try {
for (int i = 0; i < procesos.length; i++) {
procesos[i].join();
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("Entero = " + entero.get());
}
}

• produce un resultado final impredecible en el entero compartido.


Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 73
6. EXCLUSIÓN MUTUA
• Si sincronizamos el acceso al entero compartido con un bloque
sincronizado, entonces el resultado final será siempre 50000.
class Proceso extends Thread {

private Entero entero;

public Proceso(Entero entero) {


this.entero = entero;
}

public void run() {


for (int i = 0; i < 5000; i++) {
synchronized(entero) {
entero.incrementar();
}
}
}
}

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 74


6. EXCLUSIÓN MUTUA
• Java también permite emplear el modificador synchronized para
indicar que ciertos métodos de una clase se deben ejecutar bajo
exclusión mutua sobre cada objeto de esa clase, lo que proporciona
una mayor claridad conceptual de lo que representa un monitor.
SINTAXIS (II):
class <clase> {

<acceso> synchronized <metodo>(<args>) {

}

}

• lo que indica que todas las invocaciones de todos los métodos


sincronizados sobre un mismo objeto de la clase deben ejecutarse
bajo el requisito de exclusión mutua.
Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 75
6. EXCLUSIÓN MUTUA
Ej.
class Entero {

private int i;

public Entero(int i) {
this.i = i;
}

public synchronized void incrementar() {


i = i + 1;
}

public synchronized int get() {


return i;
}
}

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 76


6. EXCLUSIÓN MUTUA
class Proceso extends Thread {

private Entero entero;

public Proceso(Entero entero) {


this.entero = entero;
}

public void run() {


for (int i = 0; i < 5000; i++) {
entero.incrementar();
}
}
}

Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 77


6. EXCLUSIÓN MUTUA
class Aplicacion {
public static void main(String[] args) {
Entero entero = new Entero(0);
Proceso[] procesos = new Proceso[10];
for (int i = 0; i < procesos.length; i++) {
procesos[i] = new Proceso(entero);
}
for (int i = 0; i < procesos.length; i++) {
procesos[i].start();
}
try {
for (int i = 0; i < procesos.length; i++) {
procesos[i].join();
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("Entero = " + entero.get());
}
}

• siempre produce un resultado final de 50000 en el entero


compartido. Tema 9. PROGRAMACIÓN CONCURRENTE (I) - 78

También podría gustarte