Examen de Programación Concurrente Junio 2003
Dpto. LSIIS. Unidad de Programación.
procedure Leer_Producto (Cod : in Tipo_Codigo; Nom : out Tipo_Nombre; Prec: out Tipo_Precio); -- Acceso a la base de datos para consultar el nombre -- y el precio de un producto. procedure Actualizar_Producto (Cod : in Tipo_Codigo; Nom : in Tipo_Nombre; Prec : in Tipo_Precio); -- Acceso a la base de datos para actualizar el producto indicado por -- el código Cod con el nombre Nom y el precio Prec.
Se supone que la base de datos está implícita, y por ello no
aparece en ninguna de las llamadas anteriores.
La base de datos no tiene capacidad para acceso concurrente (es
decir, no tiene ningún tipo de bloqueo ni política de acceso
exclusivo). Sin embargo, las operaciones de lectura son
reentrantes, es decir, puede iniciarse una operación
Leer_Producto antes de que una anterior haya finalizado.
Los datos de un producto (nombre y precio) se pueden actualizar en
cualquier momento a través de Actualizar_Producto desde
un puesto central, pero esta actualización no debe llevarse a cabo
en el mismo instante en que se realiza una lectura de ese mismo
producto. Adicionalmente se requiere que los tickets entregados a
los clientes sean internamente no contradictorios: no puede
aparecer un mismo producto dos veces con nombre y/o precio
diferente.
Cada caja dispone de las siguientes operaciones:
procedure Nuevo_Ticket (I : in Natural); -- Cuando el proceso Caja(i) ejecuta Nuevo_Ticket(i), éste se queda -- bloqueado hasta la llegada de un nuevo cliente procedure Siguiente_Producto (I : in Natural; Fin: out Bool; Cod: out Tipo_Codigo); -- Siguiente_Producto(I, Fin, Cod) es ejecutado por Caja(I) -- y provoca la lectura del codigo del siguiente producto. -- Cuando no hay más productos el valor de Cod no es significativo -- y Fin adquiere el valor True.
El grafo de procesos y recursos para el sistema a desarrollar es el
siguiente:
Gestor : TipoGestor; task type Caja (I: Tipo_Rango_Cajas); task body Caja is S_Productos: Secuencia; Num_Prods_Distintos, Pos, Total_Ticket: Natural; Producto: Tipo_Producto; -- Tipo_Producto = (Codigo: Tipo_Codigo x Cantidad: Natural) Natural) Fin: Boolean; Nombre: Tipo_Nombre; Precio: Tipo_Precio; Cod_Producto: Tipo_Codigo; begin loop Nuevo_Ticket (I); Num_Prods_Distintos := 0; Crear_Vacia (S_Productos); -- bucle de registro de productos comprados loop Siguiente_Producto (I, Fin, Cod_Producto); exit when Fin; -- No hay más productos Producto.Codigo := Cod_Producto; Pos := Buscar (S_Productos, Producto); if Pos/=0 then Enesimo (S_Productos, Pos, Producto); Producto.Cantidad := Producto.Cantidad + 1; Reemplazar (S_Productos, Pos, Producto); else Producto.Cantidad := 0; Num_Prods_Distintos := Num_Prods_Distintos + 1; Insertar (S_Productos, Num_Prods_Distintos, Producto); end if; end loop; -- Se imprime el ticket Total_Ticket := 0; for K in 1..Num_Prods_Distintos loop Enesimo (S_Productos, K, Producto); Gestor.Iniciar_Lectura (Producto.Codigo); Leer_Producto (Producto.Codigo, Nombre, Precio); Gestor.Terminar_Lectura (Producto.Codigo); << Imprimir nombre del producto, precio y cantidad >> Total_Ticket := Total_Ticket + Precio*Producto.Cantidad; end loop; << Imprimir Total_Ticket >> Destruir (S_Productos); end loop; end Caja; task type Operador; task body Operador is Codigo: Tipo_Codigo; Nombre: Tipo_Nombre; Precio: Tipo_Precio; begin loop << Leer Codigo, Nombre y Precio por consola >> Gestor.Iniciar_Actualizacion (Codigo); Actualizar_Producto (Codigo, Nombre, Precio); Gestor.Terminar_Actualizacion (Codigo); end loop; end Operador;
Los procesos anteriores utilizan las operaciones del siguiente
CTADSOL:
Se pide:
Operación ejecutada | POST ampliada |
---|---|
TerminarLectura |
![]() |
TerminarActualizacion |
![]() |
Operación ejecutada | CPRE ampliada |
IniciarLectura(cod) |
![]() ![]() |
---|---|
IniciarActualizacion(cod) |
![]() ![]() |
Max_Cod_Prod : constant Natural := 100; Max_Cajas : constant Natural := 20; -- coincide con el num. de tareas lectoras Max_Lecturas : constant Natural := 20; subtype Tipo_Rango_Cajas is Natural range 1..Max_Cajas; subtype Tipo_Codigo is Natural range 0..Max_Cod_Prod; subtype Tipo_Pid_Lectura is Natural range 1..Max_Lecturas; type Tipo_Producto is record Codigo : Tipo_Codigo; Cantidad : Natural; end record; type Tipo_Acceso_Prods is array (Tipo_Codigo) of Natural; type Tipo_Lecturas_Aplzs is array (Tipo_Pid_Lectura) of Tipo_Codigo; --------------------------------------------------------------------------- protected type Tipo_Gestor is entry Iniciar_Lectura ( Codigo : in Tipo_Codigo ); entry Iniciar_Actualizacion ( Codigo : in Tipo_Codigo ); entry Terminar_Lectura ( Codigo : in Tipo_Codigo ); entry Terminar_Actualizacion ( Codigo : in Tipo_Codigo ); private -- Estado interno del recurso No_Lecturas: Tipo_Acceso_Prods := (others => 0); Lecturas_Aplzs: Tipo_Lecturas_Aplzs := (others => 0); -- sup. q el cero no se utiliza como codigo Codigo_Aplz: Tipo_Codigo := 0; Actualizando: Boolean := False; Prod_Actualz: Tipo_Codigo; Turno_Lector: Boolean := False; -- entries para operaciones aplazadas entry Iniciar_Lectura_Aplz ( Tipo_Pid_Lectura ) ( Codigo : in Tipo_Codigo ); entry Iniciar_Actualizacion_Aplz ( Codigo : in Tipo_Codigo ); end Tipo_Gestor; --------------------------------------------------------------------------- protected body Tipo_Gestor is function Siguiente_Libre return Tipo_Pid_Lectura is I : Natural := 1; begin while Lecturas_Aplzs(I) /= 0 loop I := I + 1; end loop; return I; end Siguiente_Libre;Al ejecutar un IniciarLectura, se genera dinámicamente un identificador Pid para la llamada que se va a aplazar, y se reencola esta llamada en la entry Pid de la familia IniciarLecturaAplz. Antes de reencolar, se guarda en el vector LecturasAplzs el parámetro de entrada Codigo para poder acceder a él desde la guarda de la familia de entries, que como veremos después, codificará la CPRE ampliada del apartado c.
entry Iniciar_Lectura ( Codigo : in Tipo_Codigo ) when True is Pid : Tipo_Pid_Lectura; begin Pid := Siguiente_Libre; Lecturas_Aplzs(Pid):= Codigo; requeue Iniciar_Lectura_Aplz(Pid); end Iniciar_Lectura;En el cuerpo de la entry IniciarActualizacion se guarda el parámetro de entrada en la variable CodigoAplz para poderlo consultar en la guarda de la entry privada IniciarActualizacionAplz.
entry Iniciar_Actualizacion ( Codigo : in Tipo_Codigo ) when True is begin Codigo_Aplz := Codigo; requeue Iniciar_Actualizacion_Aplz; end Iniciar_Actualizacion;Las operaciones de terminación implementan las POSTs ampliadas del apartado c.
entry Terminar_Lectura ( Codigo : in Tipo_Codigo ) when True is begin No_Lecturas(Codigo) := No_Lecturas(Codigo) - 1; Turno_Lector := False; end Terminar_Lectura; entry Terminar_Actualizacion ( Codigo : in Tipo_Codigo ) when True is begin Actualizando := False; Turno_Lector := True; end Terminar_Actualizacion;La guarda de IniciarLecturaAplz implementa la CPRE ampliada del apartado c.
-- Entradas con operaciones aplazadas entry Iniciar_Lectura_Aplz ( for Pid_Lectura in Tipo_Pid_Lectura) ( Codigo : in Tipo_Codigo ) when not (Actualizando and Lecturas_Aplzs (Pid_Lectura ) = Prod_Actualz) and (Turno_Lector or not (Iniciar_Actualizacion_Aplz'Count > 0 and Lecturas_Aplzs (Pid_Lectura ) = Codigo_Aplz)) is begin No_Lecturas(Codigo) := No_Lecturas(Codigo) + 1; Lecturas_Aplzs(Pid_Lectura):= 0; -- esta entry de la familia queda disponible end Iniciar_Lectura_Aplz;La guarda de IniciarActualizacionAplz implementa la CPRE ampliada del apartado c; cabe destacar en ella la llamada a la función ExisteLecturaAplz cuya finalidad es comprobar si existe alguna lectura aplazada del producto que se pretende actualizar.
function Existe_Lectura_Aplz ( C : Tipo_Codigo ) return Boolean is I : Natural := 1; begin while (I<=Tipo_Pid_Lectura'Last) and then not (Iniciar_Lectura_Aplz(I)'Count>0 and Lecturas_Aplzs(I) = C) loop I := I + 1; end loop; return (I<=Tipo_Pid_Lectura'Last); end Existe_Lectura_Aplz; entry Iniciar_Actualizacion_Aplz ( Codigo : in Tipo_Codigo ) when No_Lecturas ( Codigo_Aplz ) = 0 and (not Turno_Lector or not (Existe_Lectura_Aplz (Codigo_Aplz) )) is begin Actualizando := True; Prod_Actualz := Codigo; end Iniciar_Actualizacion_Aplz; end Tipo_Gestor;Otra posible implementación para la operación IniciarLectura podría comprobar la CPRE en el cuerpo de la entry IniciarLectura. De esta manera, si se cumple la CPRE, se ejecutará la operación en el mismo cuerpo de la entry IniciarLectura, y sólo en caso contrario, se reencolará la llamada en la familia de entries. Esto traerá consigo que todas las llamadas bloqueadas en la familia de entries sean lecturas del mismo producto; aprovechándonos de esto último, podríamos mejorar la eficiencia de la función ExisteLecturaAplz, y por tanto de la evaluación de la guarda de la entry IniciarActualizacionAplz.
type T_Resp_Servidor is new Character; -- no se va a utilizar package T_Canal_Producto is new Channel(Tmensaje => T_Resp_Servidor); use T_Canal_Producto;Asimismo, las lecturas aplazadas las guardaremos en una cola de llamadas. En cada elemento de esta cola guardaremos el código del producto que se desea leer, así como el canal que utilizarán la tarea Caja y la tarea Gestor para sincronizarse.
type Tipo_Lectura_Aplz is record Codigo : Tipo_Codigo; CResp : Channel_P; end record; package T_Cola_Producto is new Colas(Tipo_Elemento => Tipo_Lectura_Aplz); use T_Cola_Producto;En la implementación con paso de mensajes el recurso GestorBD lo implementaremos mediante una tarea TipoGestor, que poseerá una entry por cada operación del recurso. Las entries IniciarLectura e IniciarActualizacion tienen un parámetro de entrada adicional de tipo Channel_P por medio del cual las tareas Caja y la tarea Operador le pasarán a la tarea Gestor el canal que se utilizará para implementar el desbloqueo explícito.
task type Tipo_Gestor is entry Iniciar_Lectura ( Codigo : in Tipo_Codigo; CResp : in Channel_P ); entry Iniciar_Actualizacion ( Codigo : in Tipo_Codigo; CResp : in Channel_P ); entry Terminar_Lectura ( Codigo : in Tipo_Codigo ); entry Terminar_Actualizacion ( Codigo : in Tipo_Codigo ); end Tipo_Gestor; task body Tipo_Gestor is -- Estado interno del recurso No_Lecturas : Tipo_Acceso_Prods := (others => 0); -- sup. q el cero no se utiliza como codigo Actualizando : Boolean := False; Prod_Actualz : Tipo_Codigo; Turno_Lector : Boolean := False; -- variables para la actualizacion aplazada Cod_Act_Aplz : Tipo_Codigo := 0; CResp_Act_Aplz : Channel_P; -- cola de lecturas aplazadas Lecturas_Aplzs : Cola := Crear_Vacia; -- variables auxs Cod_Aplz : Tipo_Codigo; Cresp_Aplz : Channel_P; Llamada : Tipo_Lectura_Aplz; Nada : T_Resp_Servidor; function Existe_Lectura_Aplz (CLecturas: Cola; c: Tipo_Codigo) return Boolean is Llamada : Tipo_Lectura_Aplz; begin if not Es_Vacia(CLecturas) then Primero (CLecturas, Llamada); return (Llamada.Codigo = c); else return False; end if; end Existe_Lectura_Aplz;En el cuerpo de la rama IniciarLectura se comprueba la CPRE ampliada, y si se cumple, se desbloquea a la tarea Caja mediante un Send, y se realiza la operación IniciarLectura. Si no se cumple la CPRE, se aplaza la realización de la operación guardando en la cola Lecturas_Aplzs el registro de activación de la llamada aplazada. Cabe destacar que todas las llamadas pendientes que se guardarán en la cola se referirán al mismo producto.
begin loop select when True => accept Iniciar_Lectura ( Codigo : in Tipo_Codigo; CResp : in Channel_P ) do if not (Actualizando and Codigo = Prod_Actualz) and (Turno_Lector or not(Codigo = Cod_Act_Aplz)) then CResp.Send (Nada); No_Lecturas(Cod_Aplz) := No_Lecturas(Cod_Aplz) + 1; else Insertar(Lecturas_Aplzs, (Codigo, CResp)); end if; end Iniciar_Lectura;En el cuerpo de la rama IniciarActualizacion se aplaza siempre la operación.
or when True => accept Iniciar_Actualizacion ( Codigo : in Tipo_Codigo; Cresp : in Channel_P ) do Cod_Act_Aplz := Codigo; Cresp_Act_Aplz := Cresp; end Iniciar_Actualizacion;Los cuerpos de las ramas TerminarLectura y TerminarActualizacion implementan las POSTs ampliadas.
or when True => accept Terminar_Lectura ( Codigo : in Tipo_Codigo ) do No_Lecturas(Codigo) := No_Lecturas(Codigo) - 1; Turno_Lector := False; end Terminar_Lectura; or when True => accept Terminar_Actualizacion ( Codigo : in Tipo_Codigo ) do Actualizando := False; Turno_Lector := True; end Terminar_Actualizacion; end select;En el código de desbloqueo, que se ejecuta después de la select, se comprueba en primer lugar si se puede ejecutar la actualización aplazada. Para ello se evalúa la CPRE ampliada instanciada con la información guarda en el registro de activación de la llamada aplazada. Si no se puede desbloquear la actualización pendiente, se comprueba si se pueden ejecutar las lecturas pendientes guardas en la cola Lecturas_Aplzs. Estas llamadas pendientes se encolaron, porque en el momento en el que se realizó la llamada, no se cumplió la CPRE ampliada de la operación IniciarLectura; es decir, porque o se estaba actualizando el producto que se deseaba leer, o lo estaban leyendo otras cajas, y el operador estaba esperando y tenía el turno. En ambos casos, no se debe dar paso a las lecturas pendientes hasta que no se haya ejecutado la actualización. Esto se podrá detectar en el código de desbloqueo mirando simplemente el valor de la variable Actualizando. En el supuesto de que haya finalizado la actualización, se podrán desbloquear todas las lecturas pendientes, ya que todas ellas afectaban al mismo producto. Por eso, dentro del bucle while no es necesario comprobar la CPRE de IniciarLectura.
-- Codigo de desbloqueo -- se comprueba si se puede ejecutar -- la actualizacion aplazada if ((Cod_Act_Aplz /= 0) and then (No_Lecturas(Cod_Act_Aplz) = 0)) and (not Turno_Lector or not Existe_Lectura_Aplz (Lecturas_Aplzs, Cod_Act_Aplz)) then Cresp_Act_Aplz.Send (Nada); Actualizando := True; Prod_Actualz := Cod_Act_Aplz; Cod_Act_Aplz := 0; -- no hay actualizacion pendiente else -- se comprueba si se pueden ejecutar -- las lecturas aplazadas if not Actualizando then while not (Es_Vacia(Lecturas_Aplzs)) loop Primero (Lecturas_Aplzs, Llamada); Cod_Aplz := Llamada.Codigo; Cresp_Aplz := Llamada.CResp; Borrar (Lecturas_Aplzs); Cresp_Aplz.Send (Nada); No_Lecturas(Cod_Aplz) := No_Lecturas(Cod_Aplz) + 1; end loop; end if; end if; end loop; end Tipo_Gestor;