Septiembre 2007

Anuncio
Programación Concurrente: Septiembre 2007
David Fernández-Amorós [email protected]
El juego del laberinto concurrente se juega en un tablero rectangular de n filas y m
columnas. En cada casilla puede haber una pared, un espacio vacío o un coco. El objetivo de
cada coco es salir del laberinto. Cada casilla tiene como máximo ocho posiciones adyacentes
(incluyendo las diagonales). Un coco se va moviendo dentro del tablero, siempre que tenga
alguna casilla adyacente libre, hasta que consigue llegar a una de las casillas del rectángulo
más externo, que se consideran la salida del laberinto. El desplazamiento sólo es posible
a una casilla vacía. Cada coco mantiene una lista de las casillas por las que ha pasado. La
siguiente casilla para un coco se calcula de la siguiente manera: Se analizan las direcciones
de desplazamiento en el orden de las agujas del reloj, empezando por la dirección arriba
y a la izquierda. Si la posición actual es (x,y), entonces la primera dirección es la que lleva
a (x-1, y-1), después, la de (x,y-1), a continuación la de (x+1,y-1) y así sucesivamente. La
primera dirección que proporcione una casilla libre y que no haya sido visitada se utiliza para
moverse. Si todas las casillas vecinas han sido visitadas, entonces se levanta esta restricción
y el coco se mueve en la primera dirección, según el orden explicado, en la que haya una
casilla libre. Si no se encuentra ninguna casilla libre adyacente, el proceso de evaluación
de casillas vuelve a empezar. Las casillas adyacentes no pueden cambiar mientras dura
el proceso de evaluación de la situación, pero otros cocos pueden examinar el contenido
de esas mismas casillas. Evidentemente, dos cocos distintos no pueden moverse al mismo
tiempo hacia la misma casilla.
Se pide simular el juego del laberinto concurrente mediante un programa concurrente
escrito en pseudocódigo utilizando semáforos. Utilizar un único tipo de proceso: coco. Para
seguir la simulación, los cocos deben emitir unos mensajes por pantalla. Cada mensaje
debe incluir el número de coco, la posición que ocupa y las posiciones de las casillas que ha
visitado. Cada coco debe emitir un mensaje indicando en qué posición del tablero empieza.
Esta posición inicial se debe elegir aleatoriamente entre las casillas vacías, o bien al iniciarse
el proceso, o bien en el programa principal. También deben emitirse mensajes cada vez
que un coco decida moverse, especificando de dónde a dónde se produce el movimiento.
Los cocos también deben emitir un mensaje antes de salir del laberinto.
La simulación debe contar con unas estructuras de datos que permitan resolver razonablemente el problema. Además, se prestará atención para evitar las situaciones de interbloqueo, inanición y espera activa. Se debe procurar el máximo nivel de concurrencia entre los
procesos. Si se identifica un problema-tipo (productores/consumidores o lectores/escritores)
debe mencionarse explícitamente (incluyendo, en su caso, la prioridad más adecuada) antes
de proceder con el pseudocódigo. Es muy importante que el comportamiento de los cocos
se ajuste al enunciado, tanto en los aspectos específicamente concurrentes como en los
demás.
Es muy importante leer con atención el enunciado para responder adecuadamente a
lo que se nos pide. En principio, la forma óptima de simular el comportamiento que nos
piden dependería principalmente de la proporción de paredes frente a casillas vacías y a la
1
Programación Concurrente: Septiembre 2007
David Fernández-Amorós [email protected]
proporción de número de cocos frente a número de casillas vacías. Un programa que fuera
muy eficiente en un tablero con muchos cocos y pocas casillas libres, donde podría haber
varios cocos mirando la misma casilla, no lo sería en otro contexto donde hubiera pocos
cocos, y por tanto, pocas posibilidades de que dos cocos quisieran leer la misma casilla al
mismo tiempo. Afortunadamente, no tenemos que darle muchas vueltas a esta cuestión:
el enunciado nos dice que debe ser posible que varios cocos evalúen la misma casilla.
Esto encaja a la perfección con uno de los dos problemas-tipo de la asignatura: lectores y
escritores. El esquema de los lectores y escritores es, en la práctica, la única manera en la
que varios procesos pueden leer un recurso concurrentemente. Siempre que no se trate de
un recurso de sólo lectura, es decir, de un constante. Si es posible que los procesos puedan
leer y puedan escribir en un recurso, entonces o bien se bloquea el recurso antes de acceder
a el, o bien tenemos un esquema de lectores y escritores, en el que existen unos protocolos
de entrada a la lectura y de entrada a la escritura que preparan el acceso al recurso.
Un problema bastante frecuente es el de leer variables compartidas sin pedir exclusión
mutua. Esto es un error porque como hemos señalado en el párrafo anterior, si hay un
proceso que puede modificar la variable, no podremos saber que el valor que hemos leído
es correcto, tampoco podremos saber si ha cambiado desde el momento de la lectura.
Otra cuestión interesante es la de la granularidad. Una posibilidad, un tanto simplista,
es considerar el tablero como un único recurso. Otra posibilidad que aumenta considerablemente la concurrencia es considerar que cada casilla es un recurso distinto. Lo bueno de
este enfoque es que no es mucho más complicado. Se trata de aplicar el esquema lectores
y escritores a cada casilla. En este caso, hemos elegido la prioridad para la escritura, con el
fin de evitar la inanición, aunque también hay argumentos a favor de la prioridad para la
lectura.
program laberinto de cocos;
const N = 10; M = 15; Ncocos = 10;
// Arrays para calcular facilmente las casillas adyacentes
vecinas_x = {-1,0,1,1,1,0,-1,-1}; vecinas_y = {-1, -1, -1, 0, 1, 1, 1, 0};
var
mutex_mensaje : semaphore;
// Las variables del modelo de lectores y escritores
mutex, lector, escritor : array [1..N, 1..M] of semaphore;
nl, nle, nee : array [1..N, 1..M] of integer;
escribiendo : array[1..N, 1..M] of boolean;
// Variables para indices de bucles;
i,j : integer;
// Para guardar las posiciones iniciales de los cocos,
// no se usan fueran del programa principal
coordenada_x, coordenada_y : array[1..Ncocos] of integer;
tablero : array[1..N][1..M] of { vacia, pared, uncoco }
2
Programación Concurrente: Septiembre 2007
David Fernández-Amorós [email protected]
procedure entrada_lectura(x,y : integer);
begin
wait(mutex[x,y]);
// Si se está escribiendo o existen escritores en espera
// el lector debe ser bloqueado
if (escribiendo[x,y] or nee[x,y] > 0) then
begin
nle[x,y] := nle[x,y] + 1;
signal(mutex[x,y]);
wait(lector[x,y])
nle[x,y] := nle[x,y] - 1;
end
nl[x,y] := nl[x,y] + 1;
if (nle[x,y] > 0) then
// Desbloqueo encadenado
signal(lector[x,y])
else
signal(mutex[x,y])
end; // entrada_lectura
procedure salida_lectura(x,y : integer);
begin
wait(mutex[x,y]);
nl[x,y] := nl[x,y] - 1;
// Desbloquear un escritor si es posible
if (nl[x,y] = 0 and nee[x,y] > 0) then
signal(escritor[x,y]);
else
signal(mutex[x,y]);
end;// salida_lectura
procedure entrada_escritura(x,y : integer);
begin
wait(mutex[x,y]);
// Si se está escribiendo o existen lectores el escritor
// debe ser bloqueado
if (nl[x,y] > 0 or escribiendo[x,y]) then
begin
nee[x,y] := nee[x,y]+ 1;
signal(mutex[x,y]);
wait(escritor[x,y]);
nee[x,y] := nee[x,y]- 1;
end
escribiendo[x,y] = true;
signal(mutex[x,y])
end; // entrada_escritura
procedure cierra_escritura(x,y : integer);
begin
wait(mutex[x,y]);
escribiendo[x,y] = false;
// Desbloquear un escritor que esté a la espera.
// Si no, desbloquear un lector en espera
if (nee[x,y] > 0) then
signal(escritor[x,y]);
else
if (nle[x,y] > 0) then
signal(lector[x,y]);
else
signal(mutex[x,y]);
end // cierra_escritura
process type coco(id : integer, coor_x, coor_y : integer);
var
l,k : integer; // Variables para bucles
// La tabla es para uso interno del proceso,
//la lista es para imprimir las casillas por las que paso
tabla_visitadas : array[1..N][1..M] of boolean;
lista_visitadas : array[1..MaxLon] of record x,y : integer end;
lon_visitadas : integer; // Longitud de la lista de visitadas
// En la primera vuelta de inspección de las casillas adyacentes importa
// si las hemos visitado ya o no, en la segunda vuelta no
importa_visitadas : boolean;
salir_de_bucle : boolean; // Nos dice si ya nos hemos movido de casilla o no
estoy_mirando_casilla_adyacente_numero : integer;
coordenada_x, coordenada_y, candidata_x, candidata_y : integer;
3
Programación Concurrente: Septiembre 2007
David Fernández-Amorós [email protected]
procedure escribir_lista_visitadas;
var k : integer;
begin
for k := 1 to lon_visitadas do
write('(', lista_visitadas.x[k], ','. lista_visitadas.y[k], ') ');
writeln;
end; // escribir_lista_visitadas
begin
coordenada_x := coor_x; coordenada_y := coor_y;
for l:= 1 to N do
for k:= 1 to M do
tabla_visitadas = false;
lon_visitadas = 1;
// Primer mensaje
wait(mutex_mensaje);
writeln(’Hola, soy el coco número ',id,’ y he comenzado en la posición
(',coordenada_x, ’,’,coordenada_y,’)’);
signal(mutex_mensaje);
tabla_visitadas[coordenada_x, coordenada_y] := true;
lista_visitadas[lon_visitadas].x := coordenada_x;
lista_visitadas[lon_visitadas].y := coordenada_y;
// Mientras no estemos en el rectángulo del borde, es decir, en posición de terminar...
while coordenada_x > 1 and coordenada_x < N
and coordenada_y > 1 and coordenada_y < M)
begin
importa_visitadas := true;
salir_del_bucle := false;
while not salir_del_bucle
begin
estoy_mirando_casilla_adyacente_numero := 1;
while estoy_mirando_casilla_adyacente_numero < 9
and not salir_de_bucle
begin
candidata_x := coordenada_x +
vecinas_x[estoy_mirando_casilla_adyacente_numero];
candidata_y := coordenada_y +
vecinas_y[estoy_mirando_casilla_adyacente_numero];
if (importa_visitadas and
tabla_visitadas[candidata_x][candidata_y] = false)
or not importa_visitadas then
begin
// Miramos si la casilla candidata esta vacia
entrada_lectura(candidata_x, candidata_y);
if (tablero[candidata_x, candidata_y] = vacia) then
begin
salida_lectura(candidata_x, candidata_y);
// Pedimos bloqueo de escritura sobre la candidata
// y sobre la casilla actual
entrada_escritura(candidata_x,candidata_y);
4
Programación Concurrente: Septiembre 2007
David Fernández-Amorós [email protected]
entrada_escritura(coordenada_x,coordenada_y);
// Hay que darse cuenta de que despues de soltar el bloqueo
// de lectura, otro coco puede haberse movido a la casilla,
// por lo que nos toca comprobar si sigue vacía despues de
// obtener el bloqueo de escritura.
// Si la casilla candidata sigue vacia...
if (tablero[candidata_x, candidata_y] = vacia) then
begin
tablero[candidata_x, candidata_y] := uncoco;
tablero[coordenada_x, coordenada_y] := vacia;
salida_escritura(coordenada_x, coordenada_y);
salida_escritura(candidata_x, candidata_y);
wait(mutex_mensaje);
write(’Soy el coco ’,id,’ y me muevo de la
casilla (’,coordenada_x, ’,’, coordenada_y,’) hasta
la (’, candidata_x,’,’, candidata_y, ’) y he pasado ya por:’);
escribir_lista_visitadas;
signal(mutex_mensaje);
coordenada_x := candidata_x;
coordenada_y := candidata_y;
tablas_visitadas[coordenada_x, coordenada_y] := true;
lon_visitadas := lon_visitadas + 1;
lista_visitadas[lon_visitadas].x := coordenada_x;
lista_visitadas[lon_visitadas].y := coordenada_y;
salir_del_bucle := true;
end
else
begin
salida_escritura(coordenada_x, coordenada_y);
salida_escritura(candidata_x, candidata_y);
end
end; // if tablero[...] = vacia
else salida_lectura(candidata_x, candidata_y);
end // if (importa_visitadas...)
estoy_mirando_casilla_adyacente_numero :=
estoy_mirando_casilla_adyacente_numero + 1;
end // while estoy_mirando_casilla_adyacente_numero
if importa_visitadas = true then
// Hemos terminado la primera vuelta de inspeccion
// y seguimos sin casilla para mover
importa_visitadas := false;
else
// Hemos hecho las dos vueltas y no hemos encontrado nada
salir_del_bucle := true;
end // while not salir_de_bucle;
end // while coordenada_x ...
wait(mutex_mensaje);
writeln (’Soy el coco ’,id,’ como ya he llegado al borde del tablero me voy’);
signal(mutex_mensaje);
end // process coco
5
Programación Concurrente: Septiembre 2007
David Fernández-Amorós [email protected]
var cocos : array[1..Ncocos] of coco;
// Programa principal
begin
for i:=1 to N do
for j:=1 to M do
tablero[i][j] := vacia;
// Codigo para poner las paredes en su sitio
...
// Paredes colocadas
init(mutex_mensaje, 1);
// Ahora elegimos unas coordenadas iniciales para los cocos
for i := 1 to Ncocos do
begin
repeat
// Elegimos al azar dos numeros. La coordenada x entre 2 y N-1 y la y entre 2 y M - 1,
// no queremos que la posición inicial esté en la zona de salida del tablero
coordenada_x[i] = random (2..N-1); coordenada_y[i] = random(2..M-1);
until tablero[coordenada_x[i]][coordenada_y[i]] = vacia;
tablero[coordenada_x[i]][coordenada_y[i]] = uncoco;
end;
// Inicializamos las estructuras para los lectores y escritores
for i:= 1 to N do
for j:= 1 to M do
begin
init(mutex[i,j],1); init(escritor[i,j], 0); init(lector[i,j], 0);
nl[i,j] = 0; nle[i,j] = 0; nee[i,j] = 0;
escribiendo[i,j] = false;
end
cobegin
for i := 1 to Ncocos do
cocos[i] = coco(i, coordenada_x[i], coordenada_y[j]);
coend
end.
6
Descargar