Tareas en ADA

El último tema principal a ser introducido es tareas. Esto ha sido dejado para el final, no porque no sea importante, pero si porque, aparte de la interacción con excepciones, es una parte bastante independiente dentro del lenguaje.

    • Paralelismo

 

Hasta ahora sólo hemos considerado programas secuenciales en los cuales las instrucciones son ejecutadas en orden. En muchas aplicaciones es conveniente escribir un programa con varias actividades paralelas las cuales interactúan como se requiera. Esto es particularmente cierto en programas que interactúan en tiempo real con procesos físicos en el mundo real.
En Ada, las actividades paralelas se describen por medio de tareas. En casos simples una tarea es léxicamente descrita por una forma muy similar a un paquete. Esto consiste en una especificación que describe la interfaz presentada a otras tareas y un cuerpo que describe el funcionamiento dinámico de las tareas.

task T is                                — especificación
…..
end T;

task body T is                      — cuerpo
…..
end T;

En algunos casos una tarea no presenta interfaz a otras tareas en cuyo caso la especificación se reduce sólo a

task T;

Como un ejemplo simple de paralelismo considere una familia que va a comprar ingredientes para una comida. Suponga que necesitan carne, ensalada y vino; y la compra de esos artículos puede ser hecha al llamar a los procedimientos BUY_MEAT, BUY_SALAD y BUY_WINE respectivamente. El proceso completo  podría ser representado por

procedure SHOPPING is begin
BUY_MEAT;
BUY_SALAD; BUY_WINE;
end;

Sin embargo esta solución corresponde a la familia que compra cada artículo en secuencia. Sería más eficiente repartirse el trabajo de modo que, por ejemplo, la madre compra la carne, los niños compran la ensalada y el padre compra el vino. Y se ponen de acuerdo para encontrarse quizás  en el estacionamiento.
Esta solución paralela puede  representarse por

procedure SHOPPING is
task GET_SALAD;

task body GET_SALAD is begin
BUY_SALAD;
end GET_SALAD;

task GET_WINE;

task body GET_WINE is begin
BUY_WINE;
end GET_WINE;


begin


 

BUY_MEAT;


end SHOPPING;


 

En esta formulación, la mamá se representa como el proceso principal y llama a BUY_MEAT directamente desde el procedimiento SHOPPING. Los niños y el papá se consideran como procesos subordinados y ejecutan las tareas localmente declaradas GET_SALAD and GET_WINE las cuales respectivamente llaman a los procedimientos BUY_SALAD y BUY_WINE.
El ejemplo ilustra la declaración, activación y terminación de tareas. Una tarea es un componente de un programa como un paquete y se declara en forma similar dentro de un subprograma, bloque, paquete o en otro cuerpo de tarea. Una especificación de tarea puede también declararse en una especificación del paquete en cuyo caso el cuerpo de tarea debe declararse en el paquete del cuerpo correspondiente. Sin embargo, una especificación de tarea no puede declararse en la especificación de otra tarea, sino sólo en el cuerpo.
La activación de una tarea es automática. En el ejemplo anterior las tareas locales se activan cuando la unidad de los padre alcanza el begin que sigue a la declaración de las tareas.
Tal tarea terminará cuando alcance su final end. Así la tarea GET_SALAD llama al procedimiento BUY_SALAD y luego termina rápidamente.
Una tarea declarada en la parte declarativa de un subprograma, bloque o cuerpo de tarea se dice que depende de esa unidad. Una regla importante es que una unidad no puede ser dejada hasta que todas las tareas dependientes hayan terminado. Esta regla asegura que los objetos declarados en la unidad, y por esto mismo potencialmente visible a las tareas locales, no pueden desaparecer mientras exista una tarea que pueda accesarlos.
Es importante darse cuenta que se considera que el programa principal es llamado por una hipotética “tarea principal”. Ahora podemos indicar la secuencia de acciones cuando esta tarea principal llama al procedimiento SHOPPING. Primero se declaran las tareas GET_SALAD y GET_WINE y luego cuando la tarea principal alcanza el begin estas tareas dependientes son activadas en paralelo con la tarea principal. Las tareas dependientes llaman a sus respectivos procedimientos y finalizan. Mientras, la tarea principal llama a BUY_MEAT y luego alcanza el end de SHOPPING. Luego, la tarea principal espera hasta que las tareas dependientes hayan terminado si es que todavía no lo han hecho. Esto corresponde a la madre esperando al padre y a los niños que vuelven con sus  compras.
En el caso general la terminación ocurre en dos etapas. Decimos que una unidad se completa cuando alcanza su end final. Y se le considerará terminada cuando todas las tareas dependientes, si hay algunas, también estén terminadas. Por supuesto, si una unidad no tiene tareas dependientes entonces se completa y termina al mismo tiempo.

 

Ejercicio

Reescriba el procedimiento SHOPPING de modo que contenga  3 tareas y así revele la simetría natural de la situación. (Es decir, que las tres acciones tienen la misma jerarquía.)

 

    • El rendezvous

En el ejemplo SHOPPING las tareas no interactuaron unas con otras una vez que han sido activadas, excepto por el hecho que la unidad padre tiene que esperar a que todas terminen. Sin embargo, lo que ocurre generalemente es que las tareas interactúan y se coordinan para la consecución de un fin común. En Ada esto se ejecuta por un mecanismo conocido como un rendezvous. Esto es similar a la situación humana donde dos personas se encuentran, ejecutan una transacción y luego continúan independientemente.
Un rendezvous entre dos tareas ocurre como consecuencia de la llamada de una tarea  a una entrada
(entry)  declarada  en  otra  tarea.  Una  entrada  se    declara  de  un  manera  similar  a  como  se  declara  un procedimiento en la especificación de un paquete.

task T is
entry E( . . . );
end;

Una entrada puede tener parámetros in, out e in out al igual que un procedimiento. Sin embargo no puede entregar un resultado como una función. Una entrada es llamada de una manera similar a un procedimiento

T.E( . . .);

Un nombre de tarea no puede aparecer en una cláusula use y por este motivo se requiere la notación punto para llamar la entrada desde el exterior de la tarea. Por supuesto, una tarea local podría llamar a una entrada de sus padres directamente – se aplican las reglas de visibilidad y alcance usuales.
Las instrucciones que se ejecutan durante un rendezvous por cada entrada son descritas por instrucciones de aceptación (accept) correspondientes en el cuerpo de la tarea. Una instrucción de aceptación generalmente toma la forma

accept E( . . . ) do
– – secuencia de instrucciones


 

end E;

Los parámetros formales de la entrada E son repetidos del mismo modo que el cuerpo de procedimiento repite los parámetros formales de su declaración. El end opcionalmente es seguido por el nombre de la entrada. Una diferencia importante es que el cuerpo de las instrucciones accept es sólo una secuencia de instrucciones. Cualquiera declaración local o manejo de excepción deben ser suministradas escribiendo un bloque local.
La diferencia más importante entre una llamada de entrada y una llamada de procedimiento es que en
el caso de un procedimiento, la tarea que llama al procedimiento inmediatamente también ejecuta el cuerpo de procedimiento mientras en el caso de una entrada, una tarea llama a la entrada pero la instrucción de aceptación correspondiente es ejecutada por la tarea que posee la entrada. Además, la instrucción de aceptación no puede ser ejecutada hasta que una tarea llama a la entrada y la tarea que posee la entrada “llega hasta” la instrucción de aceptación. Naturalmente uno de estos acontecimientos ocurrirá primero y la tarea que esta involucrada (la que llama) queda suspendida hasta que otra (la poseedora de la entrada) llega a su instrucción (accept) correspondiente. Cuando esto ocurre se ejecuta la secuencia de instrucciones de la instrucción de aceptación. A esta interacción se le llama rendezvous. El rendezvous se completa cuando se alcanza el fin  de la instrucción de aceptación, luego ambas tareas proceden en forma independiente.
Podemos elaborar nuestro ejemplo de compra dando a la tarea GET_SALAD dos entradas, una para
la madre para que de dinero a los niños para ensalada y una para recoger su ensalada después. Hacemos lo mismo para GET_WINE .
También podemos reemplazar los procedimientos BUY_SALAD, BUY_WINE y BUY_MEAT por funciones que toman dinero como un parámetro y retorna el ingrediente apropiado. Nuestro procedimiento compra ahora  sería

procedure SHOPPING is
task GET_SALAD is
entry PAY(M: in MONEY);
entry COLLECT(S: out SALAD);
end GET_SALAD;

task body GET_SALAD is
CASH: MONEY; FOOD:SALAD;


begin


accept PAY(M: in MONEY) do
CASH:=M;
end PAY; FOOD:=BUY_SALAD(CASH);


accept COLLECT(S: out SALAD) do
S:=FOOD;
end COLLECT;
end GET_SALAD;

– – GET_WINE en forma similar


begin

 

 

 

end;


 

GET_SALAD.PAY (50);
GET_WINE.PAY (100);
MM:=BUY_MEAT (200); GET_SALAD.COLLECT (SS); GET_WINE.COLLECT (WW);


El resultado final es que varios ingredientes terminen en las variables MM, SS, y WW cuyas declaraciones son dejadas a la imaginación.
Conviene analizar el comportamiento lógico del ejemplo presentado. Tan pronto como las tareas GET_SALAD y GET_WINE se activan encuentran instrucciones accept y esperan hasta que la tarea principal llame las entradas PAY en cada una de ellas. Después de llamar a la función BUY_MEAT, la tarea principal llama a las entradas COLLECT. Curiosamente, la madre no esta habilitada para entregar el dinero para el vino hasta después que halla entregado el de la ensalada. Una situación análoga se presenta con la recolección.
Como un ejemplo más abstracto consideremos  el problema de crear  una tarea que actúe como un
simple buffer entre una o más tareas que producen itemes y una o más tareas que los consumen. Nuestra tarea intermedia puede mantener solo un ítem.

task BUFFERING is


 

 

end;


entry PUT(X: in ITEM);
entry GET(X: out ITEM);


task body BUFFERING is
V: ITEM;


begin


loop


 

accept PUT(X: in ITEM) do
V:=X;
end PUT;
accept GET(X: out ITEM) do
X:=V;
end GET;


end loop; end BUFFERING;
Entonces otras tareas pueden colocar  o adquirir itemes llamando BUFFERING.PUT(. . .);
BUFFERING.GET(. . .);

El almacenamiento intermedio para el ítem es la variable V. El cuerpo de tarea es una iteración infinita que contiene una instrucción de aceptación para PUT seguido por GET. De este modo la tarea acepta en forma alternada llamadas de PUT y GET las cuales llenan y vacían la variable V.
Varias tareas diferentes pueden llamar a PUT y GET y consecuentemente tendrán que ser enfiladas. Cada entrada (entry) tiene asociada una fila de tareas a la espera de llamar a la entrada – esta fila es procesada en modo FIFO y puede, por supuesto, estar vacía en un momento dado. La cantidad de las tareas en la fila de la entrada E es dado por E’COUNT pero este atributo puede ser usado sólo dentro del cuerpo de la tarea que posea la entrada.
Un entrada puede tener varias instrucciones de aceptación (generalmente sólo una). Cada ejecución
de una instrucción de aceptación remueve una tarea a la fila.
Note la asimetría intrínseca del rendezvous. La tarea que se llama debe nombrar a la tarea llamada pero no viceversa. Además, muchas tareas pueden llamar una entrada y ser puestas en fila pero una tarea puede estar sólo en una fila a la vez .
Una entrada puede no tener ningún parámetro, tal como

entry SIGNAL;

y podría luego ser llamada por T.SIGNAL;
Una instrucción de aceptación no necesita tener cuerpo como en

accept SIGNAL;

En tal caso el propósito de la llamada es simplemente efectuar una sincronización y no pasar información.
No hay restricciones sobre las instrucciones en una instrucción accept. Ellas pueden  incluir llamadas de entradas, llamadas de subprogramas, bloques e incluso otras instrucciones de aceptación (pero no para la misma entrada). Por otro lado, una instrucción de aceptación no puede aparecer en el cuerpo de un subprograma, sino que debe estar en la secuencia de instrucciones de la tarea, aunque podría estar en un bloque o dentro de otra instrucción de aceptación. La ejecución de una instrucción de retorno (return) en una instrucción de aceptación corresponde a alcanzar el final y por lo tanto termina el rendezvous.
Una  tarea  puede  llamar  a  una  de  sus  propias  entradas  pero,  por  supuesto,  esto  provocará  un
deadlock. Esto puede   parecer disparatado pero los lenguajes de programación permiten gran cantidad de cosas “tontas” tales como una iteración infinita, etc.


 

 

Ejercicios

  • Escriba el cuerpo de una tarea cuya especificación es

 

task BUILD_COMPLEX is
entry PUT_RL(X: REAL);
entry PUT_IM(X: REAL);
entry GET_COMP(X: out COMPLEX);
end;

y que alternadamente crea un número complejo por medio de las lamadas a PUT_RL y PUT_IM y luego envíe el resultado en una llamada de GET_COMP.

  • Escriba el cuerpo de una tarea cuya especificación sea

 

task CHAR_TO_LINE is
entry PUT(C: in CHARACTER);
entry GET(L: out LINE);
end;

donde

type LINE is array (1 . . 80) of CHARACTER;

La tarea actúa como un buffer que alternadamente construye una línea aceptando llamadas sucesivas de PUT y luego entrega una línea completa en una llamada de GET.

  • En INTERNET existe una página donde se puede jugar un juego de apuestas colectivo que consiste en que se le hacen preguntas a un personaje y se debe determinar su identidad.

Existe un personaje enmascarado al cual se le pueden formular preguntas, a las cuales puedo o no responder (por ejemplo, no responderá si se le pregunta su nombre). Para participar, una persona debe inscribirse: hace su apuesta y recibe una clave que le permite participar en el juego. Tiene derecho a una o más preguntas (no necesariamente consecutivas) y por cada una de ellas puede formular una identificación del personaje. Si acierta, recibe 20 veces su apuesta. Las personas que se han inscrito y no alcanzan a participar pierden su apuesta.
El programa que controla el juego tiene dos tareas paralelas: una que controla la inscripción y otra que controla el juego propiamente tal. Cuando alguien gana, las inscripciones se suspenden.

También te podría gustar...

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *