Capítulo 9

Sincronização de Alto Nível

 

Objectivos do Capítulo:

Os semáforos são essenciais na sincronização de processos, mas existem várias alternativas ou abstracções de samáforos. O capítulo aborda as primitivas de sincronização alternativas, incluindo sinais Unix, que são simples abstracções de semáforos. Para o nível de maior abstracção, são utilizados os Monitores para partilha e sincronização. Finalmente, o capítulo direcciona-se para os mecanismos de comunicação interprocessos que implementam transmissão de informação em conjuntoo com a sincronização. Todos os sistemas operativos modernos utilizam semáforos, ou pelo menos, um dos mecanismos de sincronização descritos neste capítulo.

 

9.1 Primitivas de Sincronização alternativas

Os semáforos são fundamentais no estudo dos principais conceitos de sincronização. Contudo, por vezes podem ser dificeís de implementar à medida que a aplicações paralelas vão sendo mais sofisticadas. A diferença entre implementações activas e passivas tipo V introduzem mais dificuldades nas sincronizações. Serão estudadas neste capítulo algumas alternativas de semáforos, que embora não permitam resolver problemas insolúveis por estes, possuem mecanismos que permitem acoplar vários tipos de sincronização. Esta secção descreve duas simples alternativas aos semáforos que permitem obter sincronização: sincronização AND e Eventos.

 

9.1.1 Sincronização AND

Estra baseia-se na manipulação de todos os semáforos com uma só operação, ou seja, com um Psimultâneo ou um Vsimultâneo, que bloqueiam ou libertam todos os semáforos de uma vez só. Esta medida é utilizada para evitar situações de conflito entre software de diferentes programadores. A figura 9.2 apresenta um exemplo desta aplicação.

            Figura 9.2

Semáforos simultâneos

 

semafore mutex = 1;                                                                 /* Variáveis Partilhadas */

semafore block  = 0;

 

P.sim(int S, int R) {                                           V.sim(int S, int R) {

  P(mutex);                                                          P(mutex);

   S = S-1;                                                             S = S+1;

   R = R-1;                                                            R = R+1;                                        

  if( (S<0) || (R<0) ) {                                          if(((S>=0) &&

    V(mutex);                                                             (R>=0)) &&

    P(block);                                                             ((S= =0 || (R= =0))

  }                                                                    )

  else                                                                     V(block);

    V(mutex);                                                        V(mutex);

}                                                                      }

 

 

 

9.1.2 Acontecimentos

Um acontecimento é a ocorrência de uma determinada condição ao nível do software.

            Imagine que um processo espera que uma variável alcance um determinado valor. Quando isso acontecer então o S.O. irá avisar o processo em modo de espera (à espera deste acontecimento). O acontecimento è análogo ao semáforo, esperar por este é analogo à operação P e detectar a sua ocorrência é como uma operação V.

            Uma operação “wait” coloca um processo em modo espera até à ocorrência de um acontecimento desejado, e após esta, um outro processo executa uma “operação de sinal” que restabelece o processo bloqueado. Uma outra função no controlo dos acontecimentos é o “queue” que diz se existem, ou não, processos bloqueados à espera de um acontecimento.

 

9.2 Monitores

Primitivas de sincronização por semáforos são difíceis de usar em situações de sincronização complexas. Os Monitores foram criados para simplificar os problemas da sincronização através da abstracção de alguns detalhes. Contudo, qualquer problema de sincronização que pode ser resolvido com monitores também pode ser solucionado com semáforos, ou vice-versa. Os monitores simplesmente fornecem exemplos simplificados para a sincronização que são mais fáceis de usar do que os semáforos em muitas aplicações de sincronização.

 

9.2.1 Principios de Funcionamento

Um monitor é baseado em dados do tipo abstracto, o que não é mais do que uma espécie de função que encobre acções, que tem procedimentos próprios de manipulação dessas acções e um interface com o resto do software. Um monitor é um tipo de dado abstracto em que só um processo pode ser executado a uma determinada altura. Este tipo de implementações incentiva à modularização das estruturas de dados (transformando-as em funções), o que faz com que o interface com o restante software esteja bem definido, o que evita que outros software’s tirem partido destas implementações, e permite que exista controlo sobre o desempenho do processo, desde que exista um interface específico.

            Num ambiente multi-processo podem existir resultados inconsistentes uma vez que a implementação interna dos tipos de dados abstractos é desconhecida para os programadores e dois processos poderam chamar interfaces distintos, o que pode provocar instabilidade.

 

9.2.2 Variáveis de estado

Em muitos casos, à medida que um processo vai sendo executado num monitor este vai constatar que não pode continuar até que outro processo interfira na informação protegida pelo monitor. Por exemplo, suponha que estamos a tentar resolver o segundo problema de leitores-escritores usando monitores, da forma como eles antes foram definidos. O problema leitores-escritores tem a sua forma geral na figura 9.7. A figura 9.8 mostra os procedimentos do monitor – startRead, startWrite, finishRead e finish Write – para serem executados enquanto um leitor ou um escritor entra ou sai da secção crítica.

            A solução da figura 9.8 falha por esta razão. No procedimento start, um processo irá ter acesso exclusivo ao monitor e não desistirá enquanto a secção crítica estiver a ser usada, uma vez que o processo irá esperar no ciclo while. Durante o tempo que o processo se apodera do monitor enquanto espera, este mantém controlo exclusivo do monitor, até à exclusão do processo que está à tentar executar a operação finish. É necessário permitir ao processo que está à espera libertar o monitor, enquanto mantém a sua intenção de detectar mais tarde  uma mudança no estado do monitor. Para permitir esta situação, o monitor incorpora variáveis de estado.

            Uma variável de estado é uma estrutura que pode existir dentro de um monitor e o seu valor pode ser manipulado de três formas:

 

Figura 9.7

                        Figura 9.8

-         wait. Suspende o processo em modo espera enquanto outro processo activa um signal na variável de estado.

-         signal. Retoma um outro processo que esteja suspenso devido a um wait na variável de estado. Se não existirem processos à espera, então o valor do signal não é guardado(e não terá efeito).

-         queue. Retorna o valor true se estiver pelo menos um processo suspenso na variável de estado, e de outra forma retorna o valor false.

O comportamento da operação signal distingue a versão de monitores de Hoare em relação a Brinch Hansen. Pelas linhas do monitor de Hoare, se um processo p1 está à espera de um signal numa altura em que p0 está a ser executado no monitor, p0 é suspenso enquanto que p1 começa imediatamente a ser executado no monitor. Quando p1 finaliza a sua execução no monitor, p0 continua a sua execução no monitor.A razão da forma Hoare deve-se ao facto do estado ser trueno instante em que o signal ocorre, mas pode ser false mais tarde, por exemplo, quando p0 acaba de utilizar o monitor.  Nas suas ideias iniciais, Hoare usa estas variações para provar simplesmente o correcto funcionamento dos monitores.

A variação dos monitores por Brinch Hansen têm uma aproximação passiva(Estas variações também são conhecidas como variações monitor Mesa devido à sua implementação  na linguagem de programação na Mesa Xerox.). Quando p0 executa o signal, o estado é guardado enquanto p0 continua a ser executado. Quando p0 abandona o monitor, p1 irá tentar continuar a sua execução no monitor através da alteração do estado. Isto acontece, porque mesmo que o signal indique que o acontecimento esteja a ocorrer, a situação pode ter sido alterada durante o tempo em que p0 executou o signal e p1 se apoderou da CPU. As formas de Brinch Hansen  são preferíveis uma vez que estas contém menos interrupções do que a formas de Hoare. Contudo, a performance de todo o sistema tem tendência a ser melhoor usando as implementações de Brinch Hansen.

            Com as formas de Hoare, uma situação que levará uma operação wait pode parecer com

 

...

if(resourceNotAvailable) resourceCondition.wait;

/* Now available --continue... */

...

 

Quando outro processo executar o resourceCondition.signal, um interruptor de contexto ocorre e o processo bloqueado passa a controlar o monitor e continua a executar o comando que está seguir ao if. O processo sinalizado é atrasado até que o processo em espera acabe de usar o monitor.

            Com as formas de Brinch Hansenn, a mesma situação iria ocorrer como

 

...

while(resourceNotAvailable) resourceCondition.wait;

/* Now available --continue... */

...

 

Esta parte de código garante que o estado (neste caso, resourceNotAvailable) é testado antes do processo executar o resourceCondition.wait. Nenhuma interrupção acontecerá se  o processo não abandonar voluntáriamente o monitor.

            Os monitores podem ser facilmente mal aplicados. Suponha que encobrimos uma chamada a um monitor dentro de um monitor; ou seja, apoderamo-nos das chamadas de monitores.É arriscado colocar um processo a bloquear um monitor exterior enquanto espera que um monitor interno fique dísponivel. Concerteza, outro processo pode bloquear um monitor externo que seja o mesmo monitor interno do qual o primeiro processo depende. Isto resulta num deadlock.

Um monitor é um poderoso mecânismo de alto nível para a resolução de complexos problemas de sincronização. No geral os monitores não têm sido vastamente suportados pelos sistemas operativos comercializados. Por exemplo, o Unix não suporta monitores gerais(algumas versões suportam mecanismos padronizados depois dos monitores). É difícil de apontar exactamente porque é que os monitores não têm tido sucesso comercial, uma vez que eles têm simplificado considerávelmente as soluções para os vários problemas de sincronização. (Mais uma vez, deve-se relembrar que estes não permitem o programador resolver problemas insolúveis só com  semáforos; eles apenas simplificam a solução.) Talvez a melhor implementação dos monitores tenha sido na linguagem de programação da Mesa Xerox [Lampson e Rdell, 1980]. A experiência da implementação de monitores faz realçar a dificuldade de manipular uma grande quantidade de detalhes:

 

            “ Quando os monitores são usados em sistemas reais de qualquer tamanho, uma quantidade de problemas surgem cuja resolução não tem sido encontrada: o problema dos monitores aninhados; as várias formas de definição de wait; estabelecer prioridades; lidar com situações como o fim do tempo, cancelamentos ou outras condições excepcionais; interacções com criações e destruições de de processos; monitorizar uma grande quantidade de pequenos objectos.”

 

Os programadores de sistemas operativos têm tendência a evitar a complexidade da implementação de monitores, deixando o programador comferramentas como locks, semáforos, acontecimentos e comunicação inter-processos para resolver os problemas de sincronização.

            A popularidade da implementação de aplicações concorrentes usando partes de código que correm dentro de um espaço de endereço tem alterado a natureza do problema geral. Enquanto a sincronização ultrapassa as partes de código correndo em diferentes espaços de endereço o problema é o mesmo da sincronização interprocessos descrito anteriormente, é possivel obter muitas das características de um monitor usando partes de código correndo dentro de espaços de endereço seguindo uma orientação estabelecida pelo programador. As soluções resultantes são mais fáceis de lacançar do que a sincronização baseada em semáforos e são mais simples de implementar do que os monitores, uma vez que as partes de código partilham o mesmo espaço de endereço e só uma das várias partes de código vai ser executada. Basicamente, a aproximação serve para permitir que o programa que controla um espaço de endereço possa estruturar as execuções de forma que nenhuma das partes de código viole uma secção crítica.

 

9.3 Comunicação Interprocessos

Os monitore permitem que os processos partilhem imformações através da utilização de memória partilhada dentro do monitor. Os semáforos e os sinais Unix podem ser usados para sincronizar as operações de dois processos mas não podem trocar informação entre eles (outros que não o sinal de sincronização). De agora em diante, todos os exemplos cuja informação é partilhada assumem a existência de memória partilhada para completar a partilha. Esta secção aborda os mecanismos de comunicação interprocesso baseada em mensagens (IPC) como uma forma de um processo poder partilhar informação com outros. Uma mensagem é um bloco de informação formatada por um processo de envio de mensagens de forma que esta possa ser entendida por um processo de recepção. Um mecanismo de comunicação é utilizado para copiar informação de um espaço para outro usando mensagens mas sem usar memória partilhada.

            Os sistemas modernos incluem mecanismos de protecção de memória para evitar que um processo acidentalmente ou propositadamente aceda ao espaço de memória de outro. Infelizmente, estes mecanismos também não permitem a partilha intencional de variáveis entre processos de uma aplicação. Em programações em que uma programação concorrente é feita para operar dentro de um espaço de endereço, as partes de código podem ser usadas para evitar a barreira entre espaços de endereço. Contudo, se a concorrência decidir implementar espaços de endereços, então o sistema operativo terá que ter mecanismos que copiem informação do espaço de endereço de uma aplicação para outra.. Então as aplicações poderão utilizar o sistema operativo como intermediário para partilhar informação (copiando-a) da forma referida.

            As abtracções IPC providenciam um mecanismo que permite que um processo copie informação do seu próprio espaço de endereços para uma mensagem. O mecanismo IPC então copia a informação para o espaço de endereços do processo destino(ver figura 9.12). O acto de copiar informação de um espaço para uma mensagem e da mensagem para outro espaço de endereços requere um mecanismo (que seja de confiança) do sistema operativo que viole os esquemas de protecção de memória dos dois processos. Enviar ou receber uma mensagem envolve uma intervenção do sistema operativo que contorne o mecanismo de protecção da memória.

 

9.3.1 Mailboxes

A figura 9.12 pode dar a entender que uma mensagem pode espontâneamente mudar o espaço de endereço do processo que recebe a mensagem sem que este tome conhecimento da alteração. Este paradigma é evitado, uma vez que a mensagem só é copiada para o espaço de endereços do processo quando este executar uma operação de recepção. Os buffers do sistema operativo transportam mensagens numa mailbox  com prioridades para copiar para o espaço de endereços do receptor. A figura 9.3 mostra uma vista mais detalhada da viagem da mensagem pelo sistema operativo e como a mailbox do receptor é identificada

 

Figura 9.12

 

Figura 9.13

           

A figura 9.13 mostra a mailbox para o processo Y, que está localizado no espaço do utilizador. Colocar ali a mailbox é uma decisão de design para o sistema operativo. Ou seja, deveria a mailbox estar numa parte inutilizada da espaço de endereços do processo Y, ou deveria ser guardada no espaço do sistema operativo até ser necessária para o processo Y? Se a mailbox for guardada no espaço do utilizador, então a operação receive pode ser uma rotina de livraria, uma vez que a informação será copiada de uma parte do espaço de enderessos do processo Y para outro. Contudo, o sistema de translação (compilador e restabelecedor) terá que alocar espaço em cada processo para a mailbox. Também é possível que o processo receptor possa inadvertidamente alterar partes da mailbox, destuindo links e perdendo mensagens.

            A alternativa é guardar a mailbox de Y no espaço do sistema e proteger a operação de copia até que Y execute o receive (ver figura 9.14). Esta opção coloca o controlo do espaço da mailbox no sistema operativo. Contudo, também previne a destuição inadvertida de mensagens, uma vez que a mailbox não é acedida directamente de um processo de uma aplicação. Mas esta opção requere que o sistema operativo reserve espaço de memória para as mailboxes de todos os processos. Por isso existe existe uma vigilância do número de mensagens que estão à espera de serem entregues numa altura determinada. Até ao resto do texto considere que as mailboxes são implementadas nos sistemas operativos, uma vez que é a estrutura mais habitual.

 

Figura 9.14

 

 

9.3.2 Protocolos das Mensagens

 

Para que uma mensagem faça sentido para quem a recebe, esta é formatada por um processo de envio. Para mais, deve existir um protocolo entre o processo que envia a mensagem e o que recebe de forma a que se estabeleça qual o formato desta. Por exemplo, a mensagem pode ter uma parte de estrutura C desde que ambos os processo tenham acesso a um ficheiro header que defina a estrutura. A mensagem pode ser uma string de caracteres ASCII que serão descodificados pelo receptor através da utilização de uma rotina como scanf.

            A maior parte das facilidades de uma passing-message emprega uma header para a mensagem. Esta header, que é entendida por todos os processo do sistema, identifica várias partes da informação relativa com a mensagem, incluindo a identificação do processo que a enviou, a identificação do processo que a vai receber, e do número de bytes que o conteudo da mensagem contém. Em sistemas de passagem de mensagens robustos, a mensagem pode ter um tipo, que pode ser usado para identificar mensagens que contêm informação especializada como informações de sincronização ou relatórios de erros. Na outra ponta do espectro estão mecanismos IPC (como o Unix) em que o sistema não aplica header’s ou outra informação estruturada. Em vez disso, os processos colaborantes escolhem e implementam o seu próprio protocolo.

 

9.3.3 Operações send e receive 

Existem duas opções generalizadas para o uso das operações send e receive:

-         A operação send pode ter uma forma síncrona ou assíncrona.

-         A operação receive pode ter uma forma de bloco, ou não.

 

9.3.3.1 A operação send

A operação send pode ser síncrona ou assíncrona, dependendo se o processo que envia  quer ou não sincronizar a operação com o receptor. A operação send assíncrona entrega a mensagem na mailbox do receptor e depois permite que o processo de envio continue a operação sem esperar que o destinatário receba a mensagem. Um sender que execute uma operação send assíncrona não está preocupado com a altura em que o receptor recebe a mensagem. De facto, o sender não saberá quando o receptor retirará a sua mensagem da mailbox.

            A operação send síncrona inclui uma estratégia de sincronização própria. Esta bloqueia o processo que envia até que a mensagem tenha sido recebida com sucesso pelo destinatário. Se a mensagem ficar à espera na mailbox, então o processo ficará bloqueado até que o destinatário a retire da mailbox. A transmissão de mensagens usa o mesmo paradigma básico de cooperação de todas as outras programações de produtor-consumidor. O sender é um produtor, e o receptor o consumidor. Pensando num semáforo, messageReceived (inicialmente a 0), é usado para coordenar o sender e o receptor. A operação send síncrona actua como se fosse imediatamente seguida por uma operação P(messageReceived). A operação V(messageReceived) é oculta implicitamente quando o receptor aceita a mensagem.

            Tanto a operação send sincrona como a assíncrona podem falhar em várias situações. Se o sender tentar enviar a mensagem para um processo não existente, o sistema operativo não conseguirá encontrar a mailbox onde deixar a mensagem. Como deve esta situação ser manipulada? Na operação send síncrona, um erro é enviado ao sender para que este sincronize com a ocorrência de uma condição de erro em vez da finalização do envio da mensagem. No caso da operação assíncrona, o sender continua após o envio da mensagem e não espera o retorno de qualquer valor. Sem mecanismo como os sinais Unix, não existe forma de o sistema operativo informar o sender do erro ocorrido. Por isso alguns sistemas bloqueiam numa operação send assíncrona até que a mensagem seja colocada na mailbox do receptor. Contudo, não existe sincronização implícita entre os proccesos de envio e os de recepção, uma vez que o receptor pode consultar a mailbox numa altura aleatória após a mensagem ter sido entregue.

            Uma operação receive pode ser um bloco, ou não. Uma operação receive em bloco, comporta-se como o normal ficheiro de operação read em Unix. Isto é, quando um processo executa receive, se não existir nenhuma mensagem na mailbox, o processo suspende a operação até a mensagem ser colocada na mailbox. Se a mailbox contiver uma ou mais mensagens, o bloco receive retorna imediatamente com uma mensagem. Por isso, quando a mailbox está vazia o bloco operação receive sincroniza a operação do receiver com o processo de envio. O paradigma da sincronização é como se o receptor executasse a operação P(messageTransmitted) num semáforo com o valor inicial a 0, antes de receber a mensagem. A sincronização está completa quando o sender executar implicitamente a operação V(messageTransmitted) quando a mensagem é enviada. Observe que a operação receive também é análoga a um recurso requerir um processo de calling que o bloqueie até que uma mensagem esteja disponível.

            Uma operação receive que não seja em bloco consulta a mailbox e depois retoma imediatamente o controlo do processo calling,  estejam ou não mensagens na mailbox. Este método permite que um processo receptor consulte a mailbox, e se esta nada tiver, continuar com o seu trabalho. O receptor pode sincronizar–se com uma mensagem do sender, mas não é obrigado a tal.

 

9.4 Execução de acontecimento ordenada de uma forma explícita

A secção 8.2 aborda brevemente o uso do processo criação/destruição como um mecanismo para obter a sincronização. Este facto foi ignorado durante vários anos, mas devivo ao elevado custo deste processo comparado com o uso dos semáforos., nos fins do anos 70, investigadores começaram a notar que a sincronização pode ser alcançada colocando um precedente na ocorrência de todos os acontecimentos importantes que irão ocorrer nos processos correntes. Esta filosofia não sai tão cara como a de criação/destruição, mas é mais próxima desta do que os semáforos. Seria uma vantagem acrescentada se a sincronização não necessitasse de usar memoria partilhada(por  exemplo, para controlar um semáforo, um bloco de controlo de acontecimentos, um monitor). A ideia base por trás desta técnica é de determinar os tempos em que a sincronização necessita da execução dos processos para organizar esses tempos em acontecimentos, e depois específicar a ordem adequada da execução dos acontecimentos para estar de acordo com o plano de sincronização. Embora coordenamos a actividade dos processos de acordo com a ocorrência dos acontecimentos, não usamos blocos de controlo de acontecimentos para guardar as ocorrências.

            Por exemplo, se o acontecimento x em pi não pode ocorrer até que o evento y tenha ocorrido em pj, então pode ser necessário para um deles que

                        ( ocorrência de y em pj ) < ( ocorrência de x em pi )

seja uma condição na operação de pi e de pj. De notar que muitos processos, pk, estão despreocupados com a ocorrência de x e y. Por isso, para a operação própria de pideve ser atribuida uma ordem a todos os acontecimentos dos processos. Po exemplo, os contadores de acontecimentos usam esta técnica Um contador de acontecimentos é uma variável inteira, inicialmente com o valor 0, que leva estritamente incrementos de números positivos. Um contador de acontecimentos apenas pode ser manipulado por estas funções:

-         advance. O acontecimento advance(evnt) anuncia a ocorrência de um acontecimento relacionado com o acontecimento contador de acontecimentos, provocando neste um incremento de uma unidade.

-         await. O await(evnt, v) provoca o bloqueio do processo até que evnt < V.

 

Um contador de acontecimento pode ser idealizado como um relógio global que conta cada ocorrência em que um acontecimento executa uma chamada advance, Um processo sincroniza-se com o relógio global usando o await e bloqueando até que o relógio global chegue a um tempo pré-determinado, que é, até terem exstido v chamadas advance.

É interessante   referir que advance e await não são necessários de implementar como operações indivisíveis, como no caso de funções com semáforos. S um processo é interrompido durante a excução de um advance, é importante de referir que a função irá ser terminada mais tarde e incrementar o devido contador de acontecimentos. Similarmente, o await não falha com uma interrupção.

Outra característica importante dos contadores de acontecimentos é que eles poden ser implementados fazendo réplicas destes em diferentes máquinas de uma rede, eliminando a necessidade de memória partilhada. A técnica exacta para o fazer é um tópico dos sistema operativos avançados inspirado no trabalho teórico de Lamport [1978] sobre os relógios globais.

            Com a primitiva dos relógios globais, muitos problemas de sincronização podem ser resolvidos. Contudo, a primitiva não está completa o suficiente para resolver problemas de sincronização complexos. A generalização requere uma primitiva read como uma primitiva de tipos de dados abstractos chamada sequencer. A sequencer um tópico dos sistemas operativos avançados encontrado por Red e Kanodia em 1979. Procure o artigo para encontrara uma explicação dos sequencers.

 

9.5 Sumário

O semáforos fornecem um mecanismo fundamental para alcançar a sincronização, embora sejam difíceis de implementar em alguns problemas de sincronização mais complexos. As primitivas alternativas aos semáforos são os contadores de acontecimentos, os sinais Unix e as operações P simultâneo. Estes mecanismos são simples abstracções de semáforos, logo podem sofrer algumas das críticas dos semáforos. Os monitores são uma primitiva das linguagens de alto nível para manipularem informação partilhando e sincronizando. Estes podem ter muitos seguidores mas não existem em muitos dos sistemas operativos do momento, devido à complexidade da sua implementação.

            A comunicação interprocessos abstrai a sincronização para um nível onde os mecanismos de sincronização podem transportar informação entre os processos de cooperação. Os mecanismos IPC permitem que os processos transmitam mensagens entre eles. Uma mensagem é um bloco de informação copiado do espaço de endereços de um processo para outro. O sender e o receptor tem que estar de acordo com o formato da mensagem. As operações de envio podem ser síncronas ou assíncronas, permitindo que o sender sincronize a sua operação com o receptor. As operações receive podem ser um bloco, ou não. Um receive em bloco causa que o processo de receive seja síncrono com o processo de envio no caso do receive ser executado antes do send. Os pipes são o análogo aos mailboxes nos sistemas Unix. Os contadores de acontecimentos são baseados nos acontecimentos e no que deve anteceder as suas ocorrências para uma correcta operação no conjunto de todos os processos. Nem os IPC nem os contadores de acontecimentos necessitam de memória partilhada, ao contrário de outros mecanismos de sincronização.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

                                                                                    Trabalho realizado por:

                                                                                    Miguel Ângelo Mourão Gonçalves

                                                                                    Nº 3512 do 3º Ano do Curso de

Eng. Electrotécnica do IPT