Debrucemos-nos primeiro sobre poss´ıveis integra¸c˜oes do modelo de comunica¸c˜ao en- tre processadores por envio de mensagens. Ser´a necess´ario estudar a forma como este modelo de comunica¸c˜ao pode ser integrado em classes, j´a que em sistemas orientados a objectos “puros”, os processadores s´o podem existir e realizar trabalho dentro de ob- jectos. Como foi referido no cap´ıtulo 4 (p´agina 52), este modelo de comunica¸c˜ao requer a identifica¸c˜ao de canais de comunica¸c˜ao entre os processadores. Para se fazer essa identifica¸c˜ao existem duas aproxima¸c˜oes poss´ıveis: directa ou indirecta. A primeira, na forma como foi descrita na sec¸c˜ao 4.5.2, ´e excessivamente restritiva do lado do receptor pelo que n˜ao a iremos considerar. Iremos antes apresentar uma aproxima¸c˜ao em que o receptor ´e directamente identificado pelo emissor sem que, no entanto, a identifica¸c˜ao directa inversa se verifique.
5.6.1 Identifica¸c˜ao directa do processador destino
Uma possibilidade nesse sentido ser´a associar a cada novo processador um valor enumer´avel ´unico, por exemplo do tipo inteiro, correspondendo `a sua ordem temporal de cria¸c˜ao (exemplo em pseudo-C na figura 5.6). Esta op¸c˜ao ´e, no entanto, excessi- vamente insegura j´a que n˜ao permite garantir, excepto (eventualmente) em tempo de execu¸c˜ao, uma comunica¸c˜ao formalmente correcta entre os v´arios processadores (n˜ao seria poss´ıvel garantir que a informa¸c˜ao passada ´e a esperada pelo processador recep- tor).
A linguagem Ada – que sem d´uvida ´e uma linguagem interessante – utiliza o sistema de tipos para esta tarefa, incluindo um tipo espec´ıfico para processadores, no caso designado por task.
Com esta estrat´egia, torna-se poss´ıvel ter mecanismos de comunica¸c˜ao directa entre processadores de uma forma minimamente segura (embora n˜ao completamente segura, j´a que podem existir problemas de competi¸c˜ao dessincronizada no caso de a estrutura de dados passada ao processador ser partilhada).
void proc main(void) {
// proc algorithm
}
int main(void) {
int proc; Message msg;
proc = new processor(&proc main);
send msg to proc; }
Figura 5.6: Exemplo de identifica¸c˜ao expl´ıcita de processadores com um valor inteiro.
No entanto, como ali´as ´e bem vis´ıvel no exemplo apresentado na figura 5.7, coloca- se o problema s´erio da decis˜ao sobre quais as mensagens aceit´aveis pelo processador receptor. Em Ada essa escolha ´e feita no programa do processador atrav´es da instru¸c˜ao accept (eventualmente dentro de um select para permitir v´arias escolhas) aplic´avel apenas a uma das declara¸c˜oes do tipo entry feitas na respectiva especifica¸c˜ao.
N˜ao sendo o Ada uma linguagem orientada a objectos “pura” (a vers˜ao Ada95 estendeu a vers˜ao anterior Ada83 com os mecanismos de heran¸ca e polimorfismo, mas na sua essˆencia a Ada95, tal como o C++, ´e uma linguagem h´ıbrida), poder-se-´a alegar que este mecanismo de tasks n˜ao ´e orientado a objectos.
De facto, uma situa¸c˜ao similar acontece com as linguagens baseadas em objectos activos (sec¸c˜ao 5.4.2). Esta op¸c˜ao n˜ao ´e adequada para linguagens orientadas por objectos j´a que a escolha das mensagens a aceitar pelo processador receptor nada tem a ver com o TDA do respectivo objecto. Pior do que isso, elas s˜ao aceites e executadas em tempos n˜ao est´aveis do objecto pelo que se perde a no¸c˜ao de invariante do objecto e a simplicidade na compreens˜ao e utiliza¸c˜ao do mesmo. Numa linguagem orientada a objectos, a comunica¸c˜ao com objectos faz-se pela respectiva interface, pelo que n˜ao ser´a de estranhar a inadequa¸c˜ao da identifica¸c˜ao directa do processador destino. 5.6.2 Identifica¸c˜ao indirecta
Para realizar uma identifica¸c˜ao indirecta, n˜ao amb´ıgua, de processadores no contexto de linguagens orientadas por objectos puras, poucas alternativas existir˜ao sen˜ao fazer uso dos pr´oprios objectos.
Uma aproxima¸c˜ao simples consiste em associar cada objecto, durante todo o seu tempo de vida, a um ´unico processador (que em princ´ıpio dever´a ser o processador que o criou). Na fam´ılia de linguagens do tipo “actores” (sec¸c˜ao 5.4.3) e no SCOOP (sec¸c˜ao 5.4.4) ´e esta a forma escolhida para identificar processadores. Uma mensagem enviada a um objecto que perten¸ca a outro processador ser´a uma comunica¸c˜ao directa entre os respectivos processadores. Esta op¸c˜ao tem, relativamente `a anterior, a van- tagem de ser bem adaptada aos sistemas orientados a objectos, evitando as situa¸c˜oes
-- a processor.ads
package A Processor is task type Processor is
entry Start(A Argument: in Positive);
entry Another Rendezvous;
entry Finish;
end Processor;
end A Processor;
-- a processor.adb
with Ada.Text IO;
use Ada.Text IO;
package body A Processor is task body Processor is
Done : Boolean;
begin
accept Start (A Argument: in Positive) do
Put Line("Processor started with argument: " & Positive’Image(A Argument));
end Start; Done := false;
while not Done loop select
accept Another Rendezvous do
Put Line("Rendezvous...");
end Another Rendezvous;
or accept Finish do done := true; end Finish; end select; end loop; end Processor; end A Processor; -- main.adb
with Ada.Text IO;
use Ada.Text IO;
with A Processor; procedure Main is proc: A Processor.Processor; begin proc.Start(10); proc.Another Rendezvous; proc.Finish; end Main;
muito problem´aticas de poderem existir comunica¸c˜oes entre processadores em alturas em que o invariante do objecto do processador receptor (ou seja, aquele que ter´a de processar a mensagem) pode n˜ao se verificar. Neste caso os processadores receptores s´o responder˜ao quando o respectivo objecto estiver num tempo est´avel, o que reduz drasticamente a complexidade dessas interac¸c˜oes.
Esta aproxima¸c˜ao ´e similar a uma invoca¸c˜ao remota de procedimentos (p´agina 54) aplicada a servi¸cos p´ublicos dos objectos, com a vantagem da escolha dos servi¸cos a ser remotamente invocados ser devidamente contextualizada pelo TDA dos objectos (ou seja, tirando proveito da metodologia orientada por objectos).
5.6.3 Comunica¸c˜ao s´ıncrona e ass´ıncrona
No cap´ıtulo 4 (sec¸c˜ao 4.5.1) referiu-se que, neste modelo, a comunica¸c˜ao tanto po- deria ser s´ıncrona como ass´ıncrona. Do ponto de vista da expressividade da linguagem ambas podem ser ´uteis. A comunica¸c˜ao ass´ıncrona aumenta a concorrˆencia do pro- grama j´a que permite que o processador emissor continue a execu¸c˜ao do respectivo algoritmo independentemente do processador receptor. Por outro lado, a comunica¸c˜ao s´ıncrona garante a p´os-condi¸c˜ao do servi¸co executado remotamente logo ap´os o envio da mensagem, o que pode ter consequˆencias importantes na garantia de correc¸c˜ao do algoritmo.
Uma sinergia muito interessante pode ser retirada se se tiver em considera¸c˜ao a diferen¸ca semˆantica entre servi¸cos do tipo comando e do tipo consulta (p´agina 19). Com efeito, a invoca¸c˜ao de um comando pode ser considerada uma comunica¸c˜ao di- rigida unicamente do cliente para o objecto, pelo que se adapta perfeitamente a uma comunica¸c˜ao ass´ıncrona (excepto no que diz respeito `a verifica¸c˜ao da pr´e-condi¸c˜ao como veremos `a frente). J´a a invoca¸c˜ao de uma consulta sobre um objecto ´e uma comunica¸c˜ao bidireccional pelo que se justifica que dever´a ser s´ıncrona.
Caromel [Caromel 89, Caromel 93] prop˜oe uma alternativa, designada de “espera por necessidade”3 em que a espera n˜ao ´e feita imediatamente na invoca¸c˜ao dos servi¸cos
de consulta, mas sim apenas quando o respectivo resultado ´e necess´ario. Meyer, na extens˜ao SCOOP [Meyer 97, p´agina 987], adoptou a mesma ideia. No entanto, este mecanismo de espera por necessidade pode interferir negativamente com outros meca- nismos das linguagens, em particular com os mecanismos de suporte `a programa¸c˜ao por contrato. A interferˆencia potencialmente mais gravosa ocorre com a verifica¸c˜ao da pr´e- condi¸c˜ao do servi¸co remotamente invocado (no caso, obviamente, de essa pr´e-condi¸c˜ao existir). Com efeito uma falha na pr´e-condi¸c˜ao ´e da responsabilidade do cliente (e n˜ao do objecto), pelo que permitir que a verifica¸c˜ao desta asser¸c˜ao seja ass´ıncrona com o programa do processador cliente tem efeitos extremamente negativos. Desde logo, perde-se a possibilidade de sinalizar, atrav´es de uma excep¸c˜ao, no ponto apropriado do programa desse processador a falha que ´e da responsabilidade desse mesmo processador. O resultado desta situa¸c˜ao ´e a degrada¸c˜ao da robustez do programa, podendo mesmo inviabilizar a implementa¸c˜ao de algoritmos adequados de tolerˆancia a falhas. Por es- tas raz˜oes parece-nos que, independentemente do tipo de comunica¸c˜ao ass´ıncrona (seja por invoca¸c˜ao de um comando, ou devido ao mecanismo de espera por necessidade), ´e
obrigat´orio impor a verifica¸c˜ao s´ıncrona da pr´e-condi¸c˜ao4.
No caso da aplica¸c˜ao da espera por necessidade a servi¸cos de consulta levanta- se tamb´em o problema da verifica¸c˜ao da p´os-condi¸c˜ao do servi¸co e do invariante do objecto. Esta situa¸c˜ao ´e bastante menos gravosa que no caso das pr´e-condi¸c˜oes, j´a que se pode aceitar que a eventual excep¸c˜ao (a ser propagada para o cliente) possa ser entregue no ponto de espera (em vez de ser no ponto de invoca¸c˜ao). Ser´a uma semˆantica aceit´avel para a situa¸c˜ao, embora possa causar problemas j´a que os programas dos processadores clientes ter˜ao, eventualmente, de replicar o c´odigo de gest˜ao de falhas para v´arios localiza¸c˜oes (todas as que podem esperar resultados da invoca¸c˜ao inicial).
A justifica¸c˜ao mais importante para a adop¸c˜ao deste mecanismo de espera por ne- cessidade assenta no aumento do potencial de concorrˆencia do programa, j´a que os pro- cessadores clientes podem continuar a sua agenda sem esperar “desnecessariamente”5
pelo outro processador. No entanto esse problema s´o se coloca no caso da linguagem adoptar apenas o modelo de comunica¸c˜ao entre processadores por envio de mensagens. No caso da linguagem adoptar os dois modelos (como a nossa proposta apresentada no pr´oximo cap´ıtulo), ent˜ao o potencial de concorrˆencia do programa pode ser maximizado pelo modelo de partilha de objectos. Neste ´ultimo modelo, a comunica¸c˜ao ´e s´ıncrona, pelo que n˜ao provoca nenhuma destas interferˆencias negativas com o mecanismo de excep¸c˜oes (sec¸c˜ao 5.18).