Está en la página 1de 22

PROCESAMIENTO PARALELO UNDAC

ESCUELA DE FORMACION PROFESIONAL DE SISTEMAS Y


COMPUTACION

PROCESAMIENTO PARALELO

SISTEMAS DE MEMORIA COMPARTIDA Y PROCESADO DE


MEMORIA COMPARTIDA

CERRO DE PASCO, ENERO DEL 2010

Sistemas Y Computación
PROCESAMIENTO PARALELO UNDAC

SISTEMA DE MEMORIA COMPARTIDA

Podemos emplear unas primitivas de comunicación entre procesos


que, en lugar de malgastar tiempo de procesador cuando un proceso
no puede entrar en su región crítica, bloquean la ejecución del
mismo. Una de las parejas más simples de primitivas son DORMIR
(sleep) y DESPERTAR (wakeup). DORMIR es una llamada al
sistema que bloquea al llamador, es decir, suspende su ejecución
hasta que otro proceso lo despierta. La llamada DESPERTAR tiene
como parámetro el proceso que se debe despertar.

El problema del productor-consumidor (con un buffer finito) servirá


para ilustrar el uso de estas primitivas. Especifiquemos el problema.
Un conjunto de procesos "productores" y un conjunto de procesos
"consumidores" se comunican entre sí a través de un buffer, en el
que los productores van depositando elementos, y del cual, los
consumidores los van sacando. Los productores llevan a cabo de
forma continua un ciclo "producir un elemento - guardar el elemento
en el buffer", y los consumidores repiten un ciclo parecido: "sacar un
elemento del buffer - consumir el elemento". Un productor típico
puede ser un proceso de tipo computacional que vaya colocando
líneas de resultados en un buffer, mientras que el consumidor
correspondiente puede ser el proceso que imprime estas líneas. El
buffer tiene una capacidad limitada, pudiendo almacenar sólo N
elementos del mismo tamaño. La sincronización que se requiere en
este caso es doble: en primer lugar, los productores no pueden
colocar elementos en el buffer si éste está lleno, y en segundo lugar,

Sistemas Y Computación
PROCESAMIENTO PARALELO UNDAC

los consumidores no pueden sacar elementos del buffer si éste está


vacío.

Además, el buffer y las variables compartidas deben protegerse del


acceso concurrente por parte de los distintos procesos, pues son
zonas compartidas de memoria modificables por los procesos. De
ahí que el acceso al buffer y a las variables compartidas se realice
en exclusión mutua.

Los problemas comienzan cuando el productor quiere dejar algo en


el buffer, pero éste ya está lleno. La solución es mandar a dormir al
productor y despertarlo sólo cuando el consumidor haya retirado uno
o más elementos. Análogamente, debe enviarse a dormir al
consumidor cuando trate de retirar un elemento del buffer y éste

esté vacío. Más tarde, cuando el productor deje uno o más


elementos en el buffer, despertará al consumidor (ver figura 4.1).

Sin darnos cuenta, nos hemos metido en la boca del lobo. Para llevar
la cuenta del número de elementos en el buffer, necesitamos una
variable, cuenta. El acceso a la variable cuenta no está controlado.
Así, se puede presentar la siguiente situación: el buffer está vacío y
el consumidor acaba de leer 0 de la variable cuenta. En este instante
el planificador decide pasar el control del consumidor al productor. El
productor deja un elemento en el buffer, incrementa cuenta y
observa que su valor ahora es 1, por lo que deduce que su valor era
0 y que, por tanto, el consumidor está dormido, así que el productor
llama a despertar para despertar al consumidor.

Por desgracia, el consumidor no está todavía lógicamente dormido,


de modo que la señal para despertarlo se pierde. Cuando el
consumidor se active de nuevo, comprobará el valor de cuenta que
leyó anteriormente, verá que es 0 y se irá a dormir. Por su parte, el

Sistemas Y Computación
PROCESAMIENTO PARALELO UNDAC

productor acabará llenando el buffer y también se tendrá que ir a


dormir. Ambos dormirán eternamente.

Fig 4.1 Solución equívoca al problema del productor-consumidor


mediante dormir-despertar

Para solucionar este problema, Dijkstra, en 1965, tuvo la idea de


utilizar una variable entera para contar el número de señales de
despertar puestas "en conserva" para uso futuro. Propuso el tipo de
variable llamada semáforo junto con las operaciones wait y signal
definidas sobre él. En el artículo original sobre los semáforos Dijkstra
utilizó las letras P y V, que son las iniciales de estos términos en
holandés. Nosotros utilizaremos wait y signal por ser más
descriptivos.

Sistemas Y Computación
PROCESAMIENTO PARALELO UNDAC

Un semáforo es una zona de memoria compartida que almacena un


entero no negativo sobre el cual sólo puede actuarse con una de las
operaciones siguientes:

Inicialización: Un semáforo sólo puede inicializarse una vez,


normalmente en el momento en que se define. Nosotros
representaremos la inicialización como 's  x', donde s es el nombre
del semáforo y x es un entero no negativo.

wait(s): Si s > 0 decrementa s en una unidad. Si s vale 0 provoca el


bloqueo del proceso hasta que otro proceso realice una operación
signal sobre s. Si existen varios procesos bloqueados en un
semáforo, sólo uno de ellos podrá pasar a listo tras una operación
signal sobre él. No se lleva a cabo suposición alguna sobre cuál de
los procesos bloqueados será el elegido.

signal(s): Si no hay procesos bloqueados en s, provoca el


incremento de s en una unidad. Si hay procesos bloqueados en s
(que valdrá cero), provoca la transición a listo de uno de estos
procesos.

Además, las acciones que implica una operación wait o signal se


ejecutan indivisiblemente. Esto es crucial para garantizar la
coherencia del semáforo, ya que éste no es más que una variable
compartida. Como se vio, una zona compartida de memoria no
puede ser accedida concurrentemente por más de un proceso, si al
menos uno de ellos la modifica. Volveremos sobre este tema en
breve.

Una posible implementación de los semáforos

Sistemas Y Computación
PROCESAMIENTO PARALELO UNDAC

Como se ha comentado, los mecanismos de comunicación entre


procesos son servicios del sistema operativo. La implementación de
wait y signal se va a realizar como rutinas de servicio a interrupción.
La operación wait lleva implícita la idea de que los procesos se
quedan bloqueados cuando un semáforo tiene el valor 0, y son
liberados cuando se realiza una operación signal. La forma natural
de implementar todo esto es asociando a cada semáforo una cola.
Cuando un proceso lleva a cabo una operación wait sobre un
semáforo a cero, se añade a la cola del semáforo y se le pasa a
estado bloqueado. Recíprocamente, cuando se ejecuta una
operación signal sobre un semáforo determinado, se saca un
proceso de la cola del mismo (a menos que esté vacía) y se le hace
listo. Un semáforo debe implementarse, pues, a base de dos
elementos: un entero y un puntero asociado a una cola (que puede
ser nulo).

No hemos dicho nada aún acerca del proceso que será sacado de la
cola del semáforo después de una operación signal. Tampoco
hemos dicho nada sobre el lugar donde hay que colocar un proceso
que haya efectuado un wait sobre un semáforo a cero. Para la

Sistemas Y Computación
PROCESAMIENTO PARALELO UNDAC

mayoría de semáforos es adecuada una cola en la que se siga el


criterio de que el primero en entrar sea el primero en salir (FIFO), ya
que con ello se garantizaría que todos los procesos bloqueados
puedan eventualmente hacerse listos. Lo que es importante tener en
cuenta es que distintos semáforos pueden estar asociados a colas
con criterios distintos. Ello obliga a añadir un nuevo elemento en la
implementación de un semáforo con el fin de especificar el criterio de
gestión de la cola. Este elemento puede ser un puntero a un
pequeño programa que lleve a cabo las funciones de entrada y
salida de la cola.

Indivisibilidad

Como se apuntó más arriba tanto wait como signal deben ser
operaciones indivisibles, en el sentido de que sólo un proceso debe
poder ejecutarlas en cualquier momento. Justifiquemos dicho
requisito de indivisibilidad. Supóngase que dos procesos, P0 y P1,
desean realizar la operación signal(S) sobre el semáforo S que tiene
el valor 3. Si el código expuesto más arriba no es indivisible, podría
darse la circunstancia de que el procesador se cambiara mientras
que un proceso ejecuta la instrucción 's  s + 1'. Como esta
instrucción de un lenguaje de alto nivel puede ser traducida por el
compilador a las instrucciones en ensamblador que se apuntan en la
siguiente tabla (donde 'LOAD A, S' carga el

con el contenido de la variable S, 'ADD A' incrementa el acumulador


en una unidad y 'STO S, A' almacena en la variable S el contenido
del acumulador)

Sistemas Y Computación
PROCESAMIENTO PARALELO UNDAC

la ejecución de estas instrucciones en el orden a1b1b2b3a2a3


provocaría que S valiera 4, cuando debería valer 5. El problema
básico es el mismo del ejemplo de la cuenta bancaria, P0 lee S, pero
después trabaja con un valor local (3) distinto al valor actual (4).

Se presenta un problema análogo si dos procesos ejecutan


simultáneamente una operación wait sobre un mismo semáforo, o si
se ejecuta simultáneamente un wait y un signal.

Pasemos ahora a ver cómo puede lograrse la exclusión mutua en el


código de wait y signal. Ambas operaciones deben implementarse
como rutinas que empiecen con algún tipo de operación de bloqueo,
y terminen con otra operación de desbloqueo. En una configuración
consistente en un sólo procesador, la operación de bloqueo puede
implementarse fácilmente inhibiendo el mecanismo de interrupción.
Ello asegura que el proceso no puede perder el control del
procesador central mientras esté ejecutando el wait o el signal, ya
que no hay forma de que pueda ser interrumpido. La operación de
desbloqueo se llevaría a cabo entonces, simplemente, volviendo a
autorizar las interrupciones. En una máquina con varios
procesadores centrales, este procedimiento no es adecuado, ya que
es posible que dos procesos ejecuten de forma simultánea un wait o
un signal trabajando sobre dos procesadores distintos. En este caso,
el problema se solventa desactivando las interrupciones y
encerrando el código de las operaciones en una exclusión mutua

Sistemas Y Computación
PROCESAMIENTO PARALELO UNDAC

basada en la instrucción TSL, tal como se vio en el apartado de la


exclusión mutua con espera ocupada. La utilización de la instrucción
TSL implica una espera ocupada potencial si dos procesos quieren
ejecutar simultáneamente, en procesadores distintos, una operación
sobre un mismo semáforo. Sin embargo, como las rutinas wait y
signal son muy simples, el tiempo de espera empleado en este ciclo
de espera es corto.

Es conveniente destacar que las operaciones de bloqueo y


desbloqueo implementadas mediante la instrucción TSL no pueden
emplearse como sustitutas de los semáforos para implementar la
exclusión mutua entre procesos. La espera ocupada no es aceptable
para el tiempo que podría pasar un proceso esperando que acabe la
sección crítica de otro proceso. Sin embargo, sí es aceptable para
lograr la exclusión mutua de las operaciones sobre semáforos, pues
estas tienen un código corto, esto se ilustra en la siguiente tabla

Sistemas Y Computación
PROCESAMIENTO PARALELO UNDAC

Fig 4.3. Utilización de semáforos para la Exclusión Mutua

Ejemplos de sincronización utilizando semáforos

Sistemas Y Computación
PROCESAMIENTO PARALELO UNDAC

Al principio del tema ya explicamos en qué consistía la


sincronización. Un tipo particular de sincronización lo constituye la
exclusión mutua, que puede ser lograda si los procesos colocan una
operación wait justo antes de entrar en su sección crítica, y una
operación signal inmediatamente después de su sección crítica
sobre un mismo semáforo exmut cuyo valor inicial sea 1. Obsérvese
que esta solución evita la espera ocupada.

Ejemplo 1

Dados los siguientes procesos, donde las letras representan pasos


secuenciales:

vamos a sincronizarlos de forma que el paso f de P1 no se pueda


realizar hasta que P0 haya realizado el paso b. La solución es:

Sistemas Y Computación
PROCESAMIENTO PARALELO UNDAC

Ejemplo 2

Dados los procesos del ejemplo anterior, vamos a sincronizarlos de


forma que el paso f de P1 no pueda realizarse hasta que P0 haya
realizado el paso b, y que el paso c de P0 no se pueda realizar hasta
que P1 haya realizado el paso e. Este tipo de sincronización doble,
en el que los dos procesos se sincronizan en un determinado punto
de cada proceso se llama cita. Veamos como funciona el siguiente
intento:

Sistemas Y Computación
PROCESAMIENTO PARALELO UNDAC

Esta solución no es correcta, si el proceso P1 llega a wait(s) antes de


que P0 llegue a signal(s), la sincronización se produce, si no ocurre
esto, la sincronización falla. El siguiente planteamiento sí funciona:

Por último, supóngase que un proceso desea que se le avise si


ocurre un evento específico, y que algún otro proceso es capaz de
detectar la ocurrencia de este evento. Este problema lo
resolveríamos con la utilización de un semáforo tal y se muestra en
la figura 4.4.

Sistemas Y Computación
PROCESAMIENTO PARALELO UNDAC

Fig 4.5 Ejemplo de sincronización con semáforos.

Deadlock (Interbloqueo)

Cuando varios procesos compiten por recursos es posible que se dé


una situación en la que ninguno de ellos pueda proseguir debido a
que los recursos que cada uno de ellos necesita estén asignados a
otros procesos. Esta situación se conoce en inglés con el nombre de
deadlock, nosotros utilizaremos el término interbloqueo. Otra
definición de interbloqueo es la siguiente: "un conjunto de procesos
se interbloquea si cada proceso del conjunto espera un evento que
sólo puede ser provocado por otro proceso del conjunto".

Sistemas Y Computación
PROCESAMIENTO PARALELO UNDAC

Una situación que provoca un interbloqueo es la de varios procesos


que tienen secciones críticas mutuamente excluyentes, para su
ejecución en exclusión mutua adoptan la solución de utilizar un
semáforo exmut inicializado a 1, tal como se vio un poco más arriba.
Si un proceso ejecuta un wait(exmut) dentro de una sección crítica
interbloquearía a todos los procesos que excluyen sus secciones
críticas con exmut.

Veamos otro ejemplo, dos procesos, llamados P0 y P1, utilizan dos


recursos no compartibles (tales como una impresora y un fichero
abierto en modo de escritura) X e Y. Para proteger su uso
concurrente se utilizan los semáforos x e y, en el modo antes
descrito. Estúdiese los fragmentos de código de P0 y P1 mostrados
más abajo. Ambos procesos quieren utilizar los dos recursos a la
vez. Si primero se ejecuta el código de un proceso y después el del
otro no hay ningún problema. Pero supóngase que tras realizar P0 el
wait(x) se cambia el procesador a P1, si éste ejecuta wait(y) se
producirá un interbloqueo. Es importante darse cuenta de que este
error se producirá sólo para muy pocas ejecuciones (especialmente
si estos fragmentos de código cálculos(2), cálculos(3), cálculos(5) y
cálculos(6) son pequeños), pues va a depender del orden de
asignación del procesador a los procesos (algo impredecible). Por lo

Sistemas Y Computación
PROCESAMIENTO PARALELO UNDAC

tanto, estos errores son difíciles de detectar, y cuando ocurren,


difíciles de depurar, pues para reproducir el error se debería dar una
secuencia de planificación similar, y la secuencia de planificación
dependerá de la carga del sistema.

Volviendo al ejemplo anterior del productor-consumidor veamos


cómo también una mala programación nos llevaría a un interbloqueo.
Supongamos que invertimos las operaciones wait en el código del
productor, de forma que mutex se decrementa antes que vacio. Si el
buffer está lleno, el productor se bloquearía, con mutex igual a 0. La
próxima vez que el consumidor intenta acceder al buffer, realiza wait
sobre mutex, y como vale 0, el consumidor también se bloquea.

Fig 4.6. Productor-Consumidor con semáforos (buffer de un sólo


elemento)

Sistemas Y Computación
PROCESAMIENTO PARALELO UNDAC

Fig 4.7. Solución al problema productor_consumidor con semáforos


(buffer con N elementos)

Se llega a la conclusión de que la programación con semáforos debe


realizarse con mucho cuidado para evitar los interbloqueos.

4.2.4 Monitores [DEIT93] [MILE94]

La programación mediante semáforos tiene el inconveniente de que


es fácil equivocarse al utilizarlos; además, una equivocación de este
tipo es difícil de detectar, pues sólo produce ejecuciones erróneas
ocasionalmente.

Para facilitar la escritura de programas correctos, Hoare (1974) y


Brinch Hansen (1975) propusieron una primitiva de sincronización de
alto nivel, llamada monitor. Sus propuestas tenían ligeras diferencias,
las cuales señalaremos a continuación. Un monitor es una colección
de procedimientos, variables y estructuras de datos que se agrupan
en cierto tipo de módulo o paquete. Los procesos pueden llamar a
los procedimientos de un monitor cuando lo deseen, pero no se tiene
acceso directo a las estructuras de datos internas del monitor desde
un procedimiento declarado fuera de él. Para modificar las
estructuras de datos internas del monitor es preciso utilizar los
procedimientos definidos en él. El siguiente ejemplo ilustra un
monitor escrito en un lenguaje similar a C.

Monitor ejemplo

Sistemas Y Computación
PROCESAMIENTO PARALELO UNDAC

int i;

condición c;

insertar(int item)

...

sacar(int *item)

...

Los monitores tienen una propiedad importante que los hace útiles
para conseguir la exclusión mutua: sólo uno de los procesos puede
estar activo en un monitor en cada momento. Los monitores son
construcciones del lenguaje de programación, por lo que el
compilador sabe que son especiales y puede controlar las llamadas
a los procedimientos del monitor de manera distinta a las llamadas a
los demás procedimientos. Por lo general, cuando un proceso llama
a un procedimiento de un monitor, las primeras instrucciones de éste
verificarán si hay otro proceso activo dentro del monitor. En caso
afirmativo, el proceso que hace la llamada será bloqueado hasta que
el otro proceso salga del monitor. Si no hay otro proceso que esté
utilizando el monitor, el que hace la llamada podrá entrar.

Sistemas Y Computación
PROCESAMIENTO PARALELO UNDAC

El compilador es el responsable de conseguir la exclusión mutua


sobre los datos del monitor, siendo usual que implemente dicha
exclusión con semáforos, tal como se vio en el apartado anterior. En
todo caso, el programador no es consciente del modo en que se
logra tal exclusión, él sólo debe preocuparse de determinar los datos
compartidos y las secciones críticas, y de situar todo esto en el
interior del monitor. La responsabilidad de lograr la exclusión mutua
es del compilador.

Sin embargo, los monitores no son tan sencillos de programar como


parece a primera vista. Existe un problema que aún no se ha
comentado. A veces es necesario que un proceso se bloquee dentro
del monitor. Por ejemplo, en el problema de los productores y
consumidores es posible que un productor se deba bloquear al
intentar insertar y descubrir que el buffer está lleno. Si se bloqueara
sin más, tendríamos un interbloqueo, pues ningún consumidor podrá
entrar al monitor para consumir un elemento y producir un espacio.

La solución a este problema reside en la introducción de variables de


condición, junto con dos operaciones sobre ellas, wait y signal. Si un
procedimiento de un monitor descubre que no puede continuar (por
ejemplo, cuando un productor encuentra el buffer lleno), ejecuta una
operación wait sobre alguna variable de condición, como por ejemplo
espacio. Esto hace que el proceso que entró en el monitor se
bloquee. También permite que otro proceso que tenía prohibida la
entrada al monitor pueda hacerlo ahora.

Este otro proceso, como por ejemplo, un consumidor, puede


despertar a su compañero dormido con una instrucción signal, sobre
la variable de condición que su compañero espera. Para evitar que
existan dos procesos activos en el monitor al mismo tiempo, es
necesaria una regla que indique lo que debe ocurrir después de

Sistemas Y Computación
PROCESAMIENTO PARALELO UNDAC

signal. Hoare propuso que se permitiera la ejecución del proceso que


ha despertado y que se suspendiera la ejecución del otro. Brinch
Hansen propuso que un proceso que realizara una instrucción signal
debería salir inmediatamente del monitor. En otras palabras, una
operación signal debería aparecer sólo como última instrucción de un
procedimiento del monitor. Nosotros utilizaremos la propuesta de
Brinch Hansen. Si se ejecuta un signal en una variable de condición
en la que están esperando varios procesos, sólo uno de ellos revive,
siendo determinado éste por el planificador del sistema.

Las variables de condición no son contadores. No acumulan señales


para su uso posterior, como hacen los semáforos. Así, si se hace un
signal sobre una variable de condición a la que nadie espera, la
señal se pierde. Wait debe aparecer antes de signal. En la práctica,
esto no representa ningún problema puesto que un proceso puede
verificar el valor de una variable de condición (esta operación no es
realizable sobre semáforos) para ver si es necesario realizar un
signal.

A continuación se muestra una solución al problema de los


productores y consumidores utilizando monitores. En esta solución
se utilizan los conceptos recién explicados. Está expresada en un
lenguaje parecido a C.

Sistemas Y Computación
PROCESAMIENTO PARALELO UNDAC

Sistemas Y Computación
PROCESAMIENTO PARALELO UNDAC

Con la exclusión automática de las secciones críticas, los


monitores hacen que la programación en paralelo tenga menos
tendencia a los errores que en el caso del uso de semáforos. Sin
embargo, pocos lenguajes de programación incluyen monitores,
ejemplos de estos lenguajes son Concurrent Euclid y Modula 2.

Fig 4.8. Asignación de recursos mediante monitores

Sistemas Y Computación

También podría gustarte