Unidad 3: Memoría común

Anuncio
Unidad 3 Sistemas con
memoria común
Definiciones



Por concurrencia se entiende la existencia de
varias actividades simultáneas o paralelas.
La concurrencia de procesos puede verse como
la ejecución simultánea de varios procesos.
Programación concurrente es el conjunto de
notaciones y técnicas utilizadas para describir
mediante programas el paralelismo potencial de los
problemas, así como para resolver los problemas de
comunicación y sincronización que se presentan
cuando varios procesos que se ejecutan
concurrentemente comparten recursos.
Programación de procesos concurrentes

Un programa es una secuencia de instrucciones escrita en un
lenguaje dado.

Un proceso es una instancia de ejecución de un programa,
caracterizado por su contador de programa, su estado, sus
registros del procesador, su segmento de texto, pila, datos, etc.

Un programa es un concepto estático, mientras que un proceso
es un concepto dinámico.

Es posible que un programa sea ejecutado por varios usuarios
en un sistema multiusuario, por cada una de estas ejecuciones
existirá un proceso, con su contador de programa, registros, etc.

Es decir, un proceso es un programa que se encuentra en algún
estado de ejecución.
Estados de un proceso
El estado actual de un proceso puede ser:
 Nuevo: (new) el proceso ha sido creado por un usuario al lanzar
un programa, por el sistema operativo al abrir un nuevo shell de
usuario o por cualquier otra circunstancia.
 Ejecutándose: (running) está haciendo uso del CPU.
 Listo: (runnable, ready) está esperando en una lista a ser
despachado para utilizar el procesador (CPU).
 Bloqueado: (blocked) está esperando por algún servicio que
solicitó (por ejemplo, un servicio de un dispositivo de I/O) y hasta
no ser atendido no será elegido para usar el CPU
 Suspendido o dormido: (suspended) ha recibido una señal
“suspend” y hasta que no reciba una señal “resume” no será
elegido para usar el CPU.
 Finalizado: el proceso ha terminado y todos los recursos que ha
utilizado para ejecutarse son liberados.
Concurrencia

Diremos que en un sistema existe concurrencia, o que el sistema es
concurrente, cuando se tengan varios procesos en ejecución
simultánea.
La concurrencia puede ser:
1. Real: es el caso de un multiprocesador o de un multicomputadora
(sistema distribuido), donde cada proceso dispone de un procesador
físicamente independiente para ejecutarse, y donde el problema
fundamental es mantener a los procesos comunicados entre sí.
2. Simulada o abstracta: es el caso de un monoprocesador controlado
por su sistema operativo de tiempo compartido, donde la conmutación
entre distintos procesos a muy alta velocidad proporciona la ilusión de
una ejecución concurrente.

Esto es posible gracias a la presencia de un elemento del sistema
operativo conocido como planificador, que se encarga de efectuar la
conmutación efectiva entre los distintos procesos, mediante algún
esquema de prioridades, y asignando a cada uno un máximo de tiempo
de procesador por vez, conocido como cuanto de ejecución.
Ventajas


La principal ventaja, en el caso de un modelo de concurrencia
abstracta, será la posibilidad de dedicar el tiempo de procesador
a otro proceso, si aquel que actualmente está siendo atendido
comienza a realizar una operación de entrada/salida o pasa
estado de bloqueado por cualquier otro motivo.
En el caso ideal, no existen tiempos muertos para el procesador.
Procesos en multiprogramación

Vemos que cuando el proceso A está efectuando una operación
de entrada/salida, el proceso B accede al procesador.




Un proceso se denomina secuencial si un solo hilo o
flujo de control regula su ejecución.
Los procesos pueden crear nuevos procesos (cada
uno con su propio hilo) generando así múltiples
hilos de ejecución (en principio separados).
Cada proceso tiene su propio espacio de
direccionamiento.
Entre 2 (o más) procesos algunos componentes son
disjuntos (independientes) y pueden llevarse a cabo
concurrentemente; otros podrían necesitar
comunicarse o sincronizarse entre sí.
Hilos



Un proceso puede solicitar al SO por más de un hilo
o flujo de ejecución, lo cual introduce un nivel más
fino de concurrencia: que un proceso y sus
subprocesos sean agrupados de tal forma que
compartan el mismo espacio de direccionamiento
pero cada uno tenga su propio estado local: tales
subprocesos se denominan procesos de peso ligero
o hilos (threads).
Los procesos que contienen varios hilos de
ejecución se denominan por tanto procesos de
peso pesado.
La creación de hilos puede ser dinámica ó estática
mediante un proceso de control o a su vez por otros
hilos.
Hilos
Existen dos ámbitos generales en la ejecución de hilos:
 En el espacio del usuario en la cual solamente los procesos (de
peso pesado) son visibles al SO, la administración de hilos se
realiza por un paquete o biblioteca para soporte de hilos, la cual
ofrece primitivas para:
 Creación, suspensión y eliminación de hilos,
 Asignación de prioridades y otros atributos
 Sincronización y comunicación
 La ventaja de un paquete de soporte es su portabilidad.

A nivel de kernel, son administrados por el SO nativo
directamente, ofreciendo soporte para creación y primitivas para
sincronización con mucho mayor flexibilidad y eficiencia.
Hilos, ventajas
Los hilos ofrecen las siguientes ventajas:
 Es menos caro y más eficiente en general crear varios hilos que
compartan datos dentro de un proceso que crear varios
procesos que compartan a su vez datos.
 Las operaciones de I/O en dispositivos lentos (redes, terminales,
discos) pueden ser realizadas por un hilo mientras que al mismo
tiempo otro hilo lleva a acabo cómputos útiles.
 Operaciones de coordinación y sincronización entre hilos puede
hacerse de manera muy eficiente gracias a que se realiza de
manera local, posiblemente evitando llamadas al kernel.
 Múltiples hilos pueden manejar eventos (como clicks del mouse)
en varias ventanas en un entorno GUI.
 Un servidor puede crear hilos dinámicamente para atender a las
solicitudes que recibe. A su vez los procesos clientes pueden
tener varios hilos trabajando de manera concurrente, cada uno
de los cuales se encarga de realizar una solicitud por servicios
en particular.
Construyendo hilos en Java

Existen dos formas para crear hilos adicionales (por
default, cada objeto tiene un hilo de control
implícito) en un programa Java.

La primer forma consiste en extender la clase
Thread y sobrecargar el método público run() con el
código para el nuevo hilo (El método run()
constituye propiamente el punto de entrada del hilo,
análogo al método main() para un programa). A
continuación se debe crear una instancia de tal
clase e invocar a su método start(), la máquina
virtual de Java (JVM) ejecutará al nuevo hilo:
Clase Thread
class A extends Thread{
public A(String name){
super(name); //invoca al constructor padre
}
public void run(){
System.out.println(“Me llamo “ + getName());
}
}
class B{
public static void main(String[] args){
A a= new A(“pepe”);
a.start(); //El hilo esta listo para ejecutarse
}
}
Implementar Runnable

La segunda manera consiste en implantar la
interfaz Runnable en una clase que contenga
a su vez el método público run(), crean una
instancia de la clase y pasa una referencia a
éste objeto recién creado al constructor
Thread:
class A extends Cualquier_Otra implements Runnable{
public void run(){
System.out.println(“Me llamo “ +
Thread.currentThread().getName());// *
}
}
class B{
public static void main(String[] args){
A a= new A();
Thread t = new Thread(a, “paco”);
t.start();
}
}


En ésta segunda forma, el hilo se indica de manera
explícita, pero es más flexible en el sentido que la
clase A puede extender cualquier otra clase (ya sea
del sistema o definida por el usuario), mientras que
en la primera forma esto no es posible debido a la
restricción de herencia sencilla en Java.
Noté la expresión señalada con “*”: primero se
invoca al método de clase currentThread() el cual
regresa una referencia al hilo actual en ejecución, y
a ese hilo se le invoca su método getName().

Un hilo en un programa Java puede estar en
alguno de los siete estados:







Nuevo (new),
listo (ready/runnable),
ejecutándose (running),
suspendido (suspended),
bloqueado (blocked),
suspendido-bloqueado (suspended-blocked) y
finalmente muerto (dead)
Estados de un proceso/hilo en Java y los eventos que
provocan las transiciones de estado
I/O completes
new()
sleep() expires
notify(),
notifyAll()
start()
New
Runnable
join completes
resume()
scheduled
Suspended
suspend()
yield()
timeslice
ends
Running
Blocket
sleep()
wait()
join()
blocking I/O
queue
stop()
dead
Estados de un hilo en Java

New cuando el hilo es creado, i.e.
Thread t = new Thread(a);

Runnable/ready cuando se invoca el método
start() de un nuevo hilo. Todos los hilos en
este estado son organizados por la JVM en
una estructura de datos denominada el
conjunto de hilos elegibles (runnable set).
Estados de un hilo en Java

Running el hilo está en ejecución, el código del
método run() es el punto de entrada. Si el hilo
invoca a su método yield() cede lo que le resta de
tiempo de CPU a otro hilo elegible por el
despachador de la JVM.

Suspended el hilo en alguno de los dos estados
anteriores se suspende cuando su método
suspend() es invocado, ya sea por sí mismo ó por
otro hilo. Para que pueda regresar al estado
runnable, otro hilo debe invocar al método resume()
(del hilo suspendido obviamente).
Estados de un hilo en Java

Blocked un hilo ejecutándose puede bloquearse como
consecuencia a los eventos siguientes:





Cuando invoca a su propio método sleep()
Cuando invoca a su método wait() dentro de un método indicado
como synchronized (de uso exclusivo por un sólo hilo a la vez)
de algún objeto
Cuando invoca al método join() en un objeto cuyo hilo aún no
termina
Cuando realiza una operación de servicio no inmediata
(“bloqueadora”), i.e. asociada a algún dispositivo de I/O.
Nota: yield es una indicación puramente heurística que advierte
a la JVM que si hay cualquier otro hilo ejecutable pero no hay
ninguno en ejecución el planificador debería ejecutar uno o más
de estos hilos en lugar del hilo actual.
Estados de un hilo en Java

En cada caso, el hilo queda en una lista de
espera hasta que ocurra el evento que lo
ponga en la lista de hilos elegibles a ser
ejecutados; por ejemplo, si el hilo se bloqueó
como consecuencia de invocar wait() en un
método synchronized, saldrá del estado
bloqueado cuando otro hilo invoque notify() o
notifyAll().
Estados de un hilo en Java

Suspended-blocked es un estado intermedio: si un
hilo bloqueado es suspendido por otro hilo, entra en
éste estado si la operación de bloqueo se completa
(i.e. sucede el evento para desbloqueo), entonces el
hilo entra al estado suspendido, si por otra parte el
hilo recibe la señal resume() por otro hilo antes que
el evento de desbloqueo ocurra, entonces entra al
estado bloqueado.

Dead cuando termina la ejecución del método run()
del hilo ó cuando se invoca a su método stop()
(generalmente por otro hilo).

Los mismos mecanismos para sincronización
entre hilos (semáforos, monitores, etc.) serán
usados con el mismo propósito para
coordinar procesos, por tanto, a partir de éste
momento nos referiremos de manera
indistinta al manejo de procesos e hilos.
Pérdida de actualización

Un aspecto importante en la programación
concurrente es la solución al problema denominado
pérdida de actualización que ocurre cuando 2 o más
hilos comparten datos: si 2 hilos comparten el uso
de una variable n, si ambos actualizan el valor de n
casi al mismo tiempo en una arquitectura con
instrucciones para carga/almacenamiento
(load/store en registros, y si tales operaciones son
alternadas, entonces una de las actualizaciones se
perderá al ser re-escrita por la otra).
Ejemplo

Supongamos que actualmente n=1; y cada hilo va a
ejecutar
Hilo A:
n:= n+1;

Hilo B:
n:=n+2;
Si los 2 hilos ejecutan sus enunciados anteriores
casi a la vez, n podría terminar con valor 2 o 3 en
lugar de su valor deseado, 4.

Podríamos considerar que un enunciado de asignación n:= n+a al ser
compilado se produce una secuencia de instrucciones en lenguaje de
máquina (las instrucciones en lenguaje de máquina son atómicas, i.e.
indivisibles) parecido a lo siguiente:
load n, R
add a, R
store R, n

carga el valor en la dirección n en el registro R
sumar a a R i.e. r <- R+ a
almacenar R en la dirección n
A continuación se muestra una posible situación en la cual se alterna la
ejecución del código asociado a los hilos A y B:
A: load n, R
A: add R,1
… cambio de contexto de A a B
B: load n, R
B: add R, 2
B: store R, n
… cambio de contexto de B a A
A: store R, n

El valor final de n obedece a solamente uno de los incrementos, el otro
se perdió pues fue “encimado”.

Este es un ejemplo de condición de competencia la
cual ocurre cuando 2 o más hilos comparten
estructuras de datos y están leyendo/escribiendo
sobre tales estructuras compartidas de manera
concurrente, el resultado final, que podría ser
erróneo en cuanto a lo deseado, depende de qué
hilo hizo qué en qué momento (cuando), i.e.
depende del cambio de contexto particular
(alternando de instrucciones de lenguaje máquina)
ejecutada por los hilos.

Para que un programa concurrente sea correcto, debe ser
escrito de forma tal que no dependa en ninguna forma de
cuando ocurran los cambios de contexto, ni de cuanto duren las
rebanadas de tiempo ni de las velocidades relativas de CPU; es
decir, debe de coordinarse o sincronizarse la ejecución de los
hilos de tal forma que no intenten consultar-actualizar
simultáneamente a una variable o estructura de datos
compartida.

En otras palabras, se debe evitar el cambio de contexto cuando
se están realizando instrucciones que actualizan la variable o
estructura compartida, tales acciones deben realizarse de
manera atómica.

Se denomina problema de Exclusión Mutua al proceso de
eliminar tales alternamientos indeseables. Para evitar la
presencia de condiciones de competencia y por tanto resultados
erróneos se debe identificar en cada hilo las secciones críticas
(SC), i.e., segmentos de código que:
1.
Hagan referencia a una o más variables mediante operaciones
consulta/actualización mientras alguna de tales variables está
siendo alterada por otro hilo.
Alteren a una o más variables que están siendo referenciadas o
consultadas por otro hilo.
Usen una estructura de datos (como una lista enlazada)
mientras parte de ella está siendo alterada por otro hilo.
Alteren alguna parte de una estructura de datos mientras está
siendo a su vez utilizada por otro hilo.
2.
3.
4.
Descargar