Buscar

Computação distribuída

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 3, do total de 70 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 6, do total de 70 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 9, do total de 70 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Prévia do material em texto

DESCRIÇÃO
A computação distribuída como base para desenvolvimento das aplicações atuais de
tecnologia e sua efetivação como campo de conhecimento da Ciência da Computação.
PROPÓSITO
Compreender o funcionamento dos sistemas distribuídos − no ponto de vista do
desenvolvimento das aplicações paralelas e distribuídas − e sua contribuição para acelerar as
cargas de trabalho da atualidade, bem como mensurar os impactos dos métodos aplicados
para resolução de problemas inerentes à distribuição de serviços, tais como: replicação,
transparência, consumo de mensagens, comunicação etc.
PREPARAÇÃO
Antes de iniciar este conteúdo, tenha em mãos um ambiente de programação C: Code:: Blocks,
Visual Studio, Eclipse CDT, dentre outros.
OBJETIVOS
MÓDULO 1
Analisar os conceitos básicos dos paradigmas de comunicação
MÓDULO 2
Identificar os modelos utilizados para coordenação de tempo e para garantia da transparência
de localização
MÓDULO 3
Analisar os conceitos de consistência e replicação nos sistemas distribuídos
MÓDULO 4
Identificar as principais interfaces de desenvolvimento de códigos para sistemas paralelos e
distribuídos
INTRODUÇÃO
Nas últimas décadas, a taxa de crescimento do poder de processamento de minicomputadores,
mainframes e os tradicionais supercomputadores tem sido algo em torno de 20% ao ano. E a
taxa de crescimento do poder de processamento de microprocessadores e demais sistemas de
apoio como GPUs − tipo de processador envolvendo algoritmos baseados em vetores e
matrizes, como os usados na área de processamento gráfico/jogos − tem sido algo em média
de 50% ao ano. O resultado desses avanços é a facilidade de interligar diversos computadores
por meio de um barramento externo de alta velocidade como uma rede local.
Podemos, então, definir uma nova era a partir dessas fusões de tecnologias que dão origem
aos sistemas distribuídos: a distribuição do processamento entre diversos computadores
diferentes. Muito mais do que subdividir tarefas, esse novo paradigma nos convida a criar
tarefas computacionais mais especializadas, conforme a natureza da função de cada
computador. Um exemplo clássico é a utilização do ambiente cliente/servidor usando
comunicações por sockets, chamadas de procedimentos remotos, primitivas de passagens de
mensagens, dentre muitas outras.
Neste conteúdo, aprenderemos como é tratada a computação distribuída. Como as tecnologias
convergem para entregar uma coleção de computadores autônomos, conectados por redes −
locais ou remotas −, que aparentam aos seus utilizadores como um único sistema
computacional, e em paradigmas inerentes aos fundamentos dos sistemas distribuídos, nos
quais os controles para o desenvolvimento se tornam complexos, para criar a ilusão de um
ambiente transparente, coerente, sincronizado e tolerante a falhas.
MÓDULO 1
 Analisar os conceitos básicos dos paradigmas de comunicação
TROCA DE MENSAGENS
As partes que compõem um sistema distribuído interagem pela rede para trabalhar de forma
cooperativa. As partes envolvidas trocam dados e mensagens, utilizando serviços de
comunicação fornecidos pelo sistema hospedeiro (host) e adotando protocolos de comunicação
para que possam entender uns aos outros. Essa comunicação se dá principalmente por não
existir, na maioria dos casos, uma memória física compartilhada distribuída. Portanto, nos
sistemas distribuídos, essa comunicação se dá exclusivamente por passagens de mensagens
nos agentes envolvidos por meio de protocolos e métodos próprios para conversação entre os
hosts.
O modelo cliente/servidor é a base para os sistemas de computação distribuída. Nele, um
processamento cooperativo de requisições submetidas por um cliente a um servidor que as
processa retorna um resultado, estando esses recursos de cliente e servidor espalhados sobre
vários computadores.
 Cliente/servidor.
Em um modelo cliente/servidor, temos dois agentes envolvidos nessa comunicação com
funções muito bem definidas, na qual processos servidores entregam serviços para máquinas
clientes. Com isso, o ambiente gera simplicidade e eficiência em todo o processo.
Podemos afirmar, então, que:
A troca de mensagens é um processo que envia uma mensagem pela rede destinada a um ou
mais processos.
A abrangência pode ser local ou remota, e as mensagens podem ter diversos
comportamentos, como tamanho (fixo ou variável, limitado ou não), bem como ser síncronas
ou assíncronas. 
Para aumentar a escalabilidade em um sistema distribuído, devemos fazer uso de algumas
técnicas para resolver problemas relacionados a distância com perdas e atrasos dos pacotes.
Uma delas é evitar a espera por respostas a requisições enviadas a dispositivos remotos,
permitindo seguir executando outras tarefas independentemente da resposta a ser recebida por
quem requisitou os serviços. Com isso, é possível trabalhar em outras tarefas e, com a
chegada da resposta, o sistema pode tratar de forma coerente e concluir uma requisição
enviada anteriormente. Essa técnica é denominada comunicação assíncrona.
Muitas aplicações paralelas são candidatas nativas, assim como aplicações em lote,
comumente usadas em processamento de Big Data. Elas podem ter suas execuções,
tipicamente independentes, orquestradas para execução na qual outra tarefa fica responsável
em monitorar a conclusão da comunicação.
 COMENTÁRIO
Mas nem tudo são boas notícias. Existem muitas aplicações que não podem usar a
comunicação assíncrona, principalmente aplicações interativas, quando o usuário envia uma
requisição e tem que esperar até receber uma resposta. Podemos, no entanto, resolver esse
problema entregando parte da resolução para o lado cliente executar.
COMUNICAÇÃO EM GRUPO
Coordenar sistemas distribuídos é uma das tarefas mais difíceis de se adotar devido a diversos
fatores, tais como:
Computadores podem falhar ou ficar indisponíveis por algum outro motivo.
Novas máquinas podem ser inseridas e/ou removidas do sistema.
Linhas de comunicações não são 100% confiáveis.
Eventos podem ocorrer de forma concorrente.
Em comunicações com chamadas a procedimentos remotos (RPC – Remote Procedure Calls),
envolvemos somente dois processos que permitem que programas chamem procedimentos
localizados em outras máquinas.
Quando um processo na máquina A chama um procedimento na máquina B, o transmissor A
será suspenso enquanto estiver executando o procedimento em B.

As informações são transportadas do transmissor para o receptor por parâmetros, e retornam o
resultado da execução do procedimento. Assim, nenhuma troca de mensagens será visível ao
programador.
A ideia é fazer com que uma chamada de procedimento remoto pareça o mais possível com
uma chamada local, tendo que ser transparente. Sendo assim, o procedimento de chamada
não precisa estar ciente de que o procedimento enviado está em execução em uma máquina
diferente, e vice-versa.
 ATENÇÃO
Esse processo, no entanto, gera um problema: ele não consegue ser executado por diversas
máquinas ao mesmo tempo. Sempre ficará restrito a duas entidades, um transmissor e um
receptor.
Como podemos, então, resolver esse problema?
A resposta é simples: comunicação em grupos.
Com uma única operação, podemos enviar uma mensagem a vários destinos, permitindo que
processos em um grupo sejam tratados como uma única abstração. Esse conjunto de
processos cooperam entre si para prover um serviço, coordenando a troca de mensagens entre
os membros do grupo e dos processos externos com o grupo.
Os grupos são dinâmicos, por isso novos grupos podem ser criados e os antigos podem ser
destruídos.
Um processo pode juntar-se a um grupo ou deixá-lo, e é possível ser membro de diversos
grupos ao mesmo tempo.
São necessários, portanto, mecanismos para gerenciar grupos e os processos participantes
dos grupos.
Quando usamos mecanismos de comunicação em grupo, os processos tratam com coleções
de outros processos de forma abstrata.
Se um processo envia uma mensagem para um grupo de servidores,esse processo não
precisa se preocupar com quantos são os servidores nem onde eles estão localizados.
Já na chamada seguinte, o número de servidores desse grupo e suas localizações podem
mudar, sendo essa alteração transparente para o processo, que envia a mensagem para o
grupo.
São aplicações de comunicação em grupos:
Servidores altamente disponíveis e confiáveis.
Replicação de bancos de dados.
Conferências multimídia.
Jogos distribuídos.
Aplicações que em geral necessitem de uma alta taxa de disponibilidade, confiabilidade e
tolerância a falhas.
A comunicação pode ser implementada usando os conceitos de multicast, broadcast ou
unicast:
MULTICAST
Os pacotes são enviados de uma só vez para todos os processos de um grupo.
BROADCAST
Os pacotes são enviados para todas as máquinas, e somente os processos que fazem parte do
grupo não os descartam.
UNICAST
Transmissão estilo ponto a ponto, na qual o processo tem que enviar mensagem para cada
membro do grupo.
A maneira como será implementada a comunicação em grupo depende das características
envolvidas na interligação dos equipamentos. Em algumas redes, é possível criar um endereço
especial no qual várias máquinas podem escutar. Quando uma mensagem é enviada para um
desses endereços, automaticamente ela será recebida por todas as máquinas que estão
escutando esse endereço. Essa técnica é chamada de multicasting, e implementá-la é
simples: basta atribuirmos um endereço de multicasting para cada grupo.
 Comunicação multicast.
Temos, ainda, redes que suportam a comunicação em broadcasting, na qual as mensagens
contêm um determinado endereço que é escutado por todas as máquinas (broadcast global) ou
direcionado a uma sub-rede específica (broadcast local).
 COMENTÁRIO
O broadcasting pode ser usado para implementar comunicação em grupo, embora não seja tão
eficiente. Isso porque todos na rede irão receber as mensagens, e cada processo terá que
avaliar se aquela mensagem recebida é para o grupo ao qual ele pertence ou não. Nesse caso,
o processo terá gasto recurso de tempo e de processamento de forma desnecessária. Mesmo
assim, temos a vantagem que uma única mensagem enviada poderá atingir a todos os
equipamentos de uma determinada rede.
 Comunicação broadcast.
Quando a comunicação é um a um, a denominamos de unicasting.
 Comunicação unicast.
Além das características normais encontradas nos mecanismos de troca de mensagens − tais
como bufferização e blocagem −, no tratamento de grupos temos novas características de
organização, endereçamento e outras que serão detalhadas a seguir.
Podemos ter grupos fechados, nos quais somente os membros podem mandar mensagens
para os outros membros, que geralmente são utilizados para execução paralela.
 ATENÇÃO
Os processos de fora não podem enviar mensagens para o grupo como um todo, embora
possam enviar mensagens para membros individuais.
E podemos ter os grupos abertos, nos quais qualquer processo pode enviar uma mensagem
para qualquer grupo. Normalmente, esse tipo de mensagem é muito utilizado em servidores
replicados.
 Grupos fechados x grupos abertos.
Quanto à organização, possuímos:
GRUPOS PARES OU SEMELHANTES
Todos os processos são tratados como iguais ou pares, e as decisões são tomadas
coletivamente. Essa característica está relacionada à estrutura interna do grupo. Em alguns
grupos, os processos são iguais ou semelhantes, nenhum processo é gerente e todas as
decisões são feitas coletivamente.
GRUPOS HIERÁRQUICOS
Um processo é o coordenador e os demais estão subordinados a ele − uma visão
mestre/escravo. Nesse caso, quando uma requisição é feita, seja por um cliente externo ou por
um dos membros do grupo, ela é enviada ao coordenador. Este decide, então, qual dos
processos do grupo é o mais indicado para executar a solicitação.
 Grupos semelhantes x hierárquicos.
Cada uma dessas organizações tem suas próprias vantagens e desvantagens.
A vantagem é que na comunicação em grupos de semelhantes não existe um ponto único de
falha. Se um dos processos falhar, o grupo continua, apenas se torna menor.

A desvantagem é que a tomada de decisão é mais complicada. Qualquer decisão precisa
envolver todos do grupo, gerando uma demora e uma sobrecarga na comunicação e dos
eventos.
No grupo hierárquico temos o oposto, já que todas as decisões são tomadas pelo coordenador,
como qual processo do grupo vai executar qual atividade.
Quando um processo termina, ele avisa ao coordenador que está pronto para outra atividade.
A perda do coordenador em um grupo hierárquico, portanto, causa uma parada nas atividades
do grupo.
Nesse caso, algum algoritmo pode ser usado para eleger um processo coordenador, se o
principal falhar.
 ATENÇÃO
Em um mecanismo de comunicação em grupo, é necessário um controle sobre a criação e
destruição de grupos, bem como permitir que processos se integrem a eles grupo ou os
deixem.
Um enfoque é ter um servidor de grupo para o qual todas as requisições possam ser feitas.
Esse servidor pode manter um controle total de todos os seus grupos e sobre quais
participantes pertencem a qual grupo, estratégia essa concentrada no modelo centralizado.
Fácil de implementar, eficiente e de rápido entendimento.
Infelizmente, ele compartilha com a maior desvantagem das técnicas centralizadas: um único
ponto de falha. Se o servidor de grupos falhar, o gerenciamento do grupo deixa de existir.
Provavelmente, a maioria dos grupos terá que ser reconstruída, interrompendo o trabalho que
estava em andamento.
O método oposto é gerenciar os grupos de forma distribuída.
Em um grupo aberto, um processo de fora pode enviar uma mensagem para todos
anunciando a sua presença.

Um grupo fechado só aceita mensagem para todos de algum processo de fora se for uma
solicitação para participar do grupo.
Uma mensagem direta sem antes estar participando do grupo fechado não seria aceita. Para
um processo deixar de fazer parte de um grupo, basta enviar uma mensagem de adeus
(goodbye) a todos.
Consideram-se, ainda, duas características relativas ao controle dos participantes do grupo.
Se um membro falhar, ele efetivamente deixa de pertencer ao grupo. O problema é que pode
não existir um aviso a todos os outros membros, como quando o processo deixa o grupo de
forma padrão. Os demais membros têm que descobrir por conta própria que aquele
participante não está respondendo e o excluir do grupo.
Outro ponto é o sincronismo necessário entre as mensagens sendo enviadas e a entrada ou
saída de um membro no grupo.
Uma última preocupação relacionada ao controle dos participantes do grupo é quando muitas
máquinas param de forma anormal.
 EXEMPLO
Quando ocorre um particionamento da rede, deixando o grupo inoperante. Algum protocolo se
faz necessário para reconstruir o grupo com as máquinas que restaram.
Podemos citar, ainda, outros aspectos importantes em uma comunicação em grupos, como
atomicidade ou broadcast atômico, em que uma mensagem chegará a todos os membros do
grupo ou para nenhum (ou tudo ou nada).
Quando qualquer processo enviar uma mensagem para um grupo, ele não deve preocupar-se
se algum outro membro do grupo não recebeu essa mensagem.
Com essa propriedade da atomicidade, o processo receberá uma mensagem de erro caso um
ou mais integrantes do grupo tenham tido problemas no recebimento.
Do ponto de vista de programação, tudo se passa como se fosse enviado para apenas um
receptor.
Outra propriedade importante é a ordenação de mensagens. As mesmas podem chegar
desordenadas, e precisaríamos de um sistema de buffer para armazená-las e tratá-las de
forma adequada.
 EXEMPLO
Por exemplo, adotar uma postura de ordenação global − que entrega todas as mensagens
exatamente na ordem na qual foram criadas − ou ordenação consistente − na qual o sistema
decide qual mensagem precede outra ‒, quando as mensagens são enviadas quase ao mesmo
tempo, e entregar as mensagens nessa ordem.
Um mecanismo de comunicação em grupo precisater uma semântica bem definida com
relação à ordem em que as mensagens serão entregues. A melhor garantia para isso é fazer
com que as mensagens sejam entregues na mesma ordem em que foram enviadas.
 COMENTÁRIO
Como referência de estudo do ambiente de controle de sistemas de gerenciamento de cluster
focados em Big Data, existe a ferramenta ZooKeeper, que mostra uma visão prática de uma
coordenação de eventos e comunicação efetiva em sistemas distribuídos.
CÓDIGO MÓVEL
Em sistemas distribuídos tradicionais, os componentes de aplicações são essencialmente
estáticos.
O código móvel denomina um conjunto de tecnologias de linguagem e plataforma de sistemas
distribuídos que suportam a construção de programas de computador instalados em
computadores servidores.
Esses programas são transferidos, sob demanda, para os computadores clientes e
automaticamente executados, da forma mais segura possível, para eles. Ou seja, executam
atualizações dinâmicas fazendo injeção de código em programas/componentes em execução,
adaptando-se a condições do processo de execução.
Um componente é instanciado em um determinado nó do ambiente e permanece neste durante
todo seu ciclo de vida.
1
Interações remotas entre componentes se dão por meio de troca de mensagens pela rede.
As tecnologias que suportam mobilidade de código permitem que trechos de código possam
ser transmitidos pela rede para serem executados em locais remotos.
2
3
A migração não é transparente aos desenvolvedores de aplicações distribuídas, mas
explicitamente manipulada por eles para atingir objetivos próprios da aplicação.
Mobilidade de código fornece flexibilidade para o gerenciamento da configuração de código em
um ambiente distribuído, e pode oferecer vantagens para a execução de atividades
distribuídas. A adequação de applets Java para a modelagem de interações com usuários
finais, por exemplo, é observável pelo número de aplicações na Internet que utilizam essa
tecnologia.
Os applets são pequenos programas Java, criados pela Sun Microsystems em 1995, que são
instalados em servidores Web e referenciados por páginas HTML. Quando um browser acessa
essa página HTML que referencia o applet, ele automaticamente também transfere o código do
applet e o executa.
Os applets são, nos dias atuais, os principais responsáveis pela grande disseminação do
conceito e das tecnologias de código móvel em geral.
Grande parte dos esforços para o suporte a código móvel se destina ao desenvolvimento de
sistemas de agentes móveis e de aplicações baseadas em tais sistemas. Um agente móvel
(ou simplesmente agente) é um elemento de software autocontido responsável pela execução
de uma tarefa, capaz de migrar de forma autônoma em um sistema distribuído. Um agente
migra em um ambiente distribuído de um local lógico − denominado neste conteúdo de agência
− a outro.
Quando um agente migra, sua execução é suspendida na sua origem. Ele é transportado (isto
é, seu código, estado de dados, estado de execução e informação de controle) para outro
componente no sistema distribuído, no qual sua execução é retomada. Ou seja, o modelo de
código móvel basicamente desloca a carga computacional para a máquina cliente.
 COMENTÁRIO
Como percebemos até o momento, do ponto de vista da segurança, temos diversos problemas
associados, e aos poucos esse modelo de computação distribuída vem sendo tratado com
muito cuidado pelo mercado corporativo.
Como características acopladas à tecnologia de código móvel, podemos apontar o uso de uma
execução segura dentro de um sandbox, no qual o código é trazido de um servidor qualquer,
denominado código estrangeiro, e executado em um espaço de nomes restrito (o sandbox).
Dessa forma, na execução de seu código, ele terá acesso limitado à CPU, à memória e a
quaisquer outros recursos computacionais gerenciados pelo sistema operacional por meio de
métodos de controle de acesso lógico.
 EXEMPLO
Podemos descrever os applets Java que, quando em execução dentro de um sandbox,
apresentam diversas restrições.
Já o modelo de código móvel usando ActiveX não utiliza o conceito de sandbox. Sua
segurança de execução é baseada no processo de autenticação da procedência do código por
assinaturas digitais validadas por uma entidade certificadora por intermédio de certificados
digitais. Assim, o programa executa no ambiente operacional do usuário sem qualquer restrição
computacional.
 ATENÇÃO
Para um código ser móvel, ele deve possuir uma visão completa de abstração, de tal forma que
não fique preso a detalhes e dependências do sistema operacional e/ou ao hardware
escolhido. Ou seja, deve ser aderente em múltiplas plataformas e sistemas operacionais.
Por meio de um projeto de máquina virtual consistente, é possível se garantir a execução
homogênea de um mesmo código móvel em várias plataformas específicas sem que seja
necessário se recompilar esse código.
 EXEMPLO
Programas construídos na linguagem Java são compilados para um formato de código
intermediário, chamado de bytecode. A máquina virtual Java que interpreta esses bytecodes é
um microprocessador, implementado em software. Ele interpreta um conjunto de
aproximadamente 160 instruções de uma máquina virtual especialmente projetada para
executar programas Java.
A execução de programas de código móvel é feita sob demanda dos clientes. Sendo assim,
não há como prever quando um programa disponibilizado em um servidor será utilizado por
quaisquer clientes.
Para que funcione adequadamente sob essas condições de imprevisibilidade, todas as etapas
precisam ocorrer da forma mais automática e rápida possível, sem que o usuário esteja
envolvido em uma série de questionamentos sobre a configuração local do programa.
As etapas são:
Transmissão do código (do servidor para o cliente).

Instalação do código (ativação da máquina virtual, carga do sandbox, verificação da
consistência do código recebido, ligação entre o código recebido e as bibliotecas locais do
cliente).

Execução do código, com a criação de processos, alocação de memória para dados e
instruções etc.
Com isso em mente, entregamos transparência e desempenho aceitável na transmissão,
instalação e execução do código.
ORIENTAÇÃO A EVENTOS
Um evento pode ser definido como uma mudança significativa do seu estado.
 EXEMPLO
Quando um consumidor adquire um imóvel, o estado dele se modifica de "à venda" para
"vendido".
A arquitetura desse sistema pode tratar essa mudança de estado como um evento cuja
ocorrência pode ser divulgada para outros aplicativos dentro da sua arquitetura.
CONCEITOS DE SISTEMAS DE MENSAGENS
De uma perspectiva formal, o que é produzido, publicado, propagado, detectado ou consumido
é uma mensagem (geralmente assíncrona), chamada de notificação de evento, e não o
próprio evento, que é a mudança de estado que acionou a emissão da mensagem. Isso se
deve às arquiteturas orientadas a eventos, muitas vezes sendo projetadas sobre arquiteturas
orientadas a mensagens, nas quais tal padrão de comunicação requer que uma das entradas
seja somente texto (a mensagem), para diferenciar como cada comunicação deve ser tratada.
Um sistema de mensagens é um dos mecanismos mais comumente usados para troca de
informações entre aplicações.
Ao escolher seu mecanismo de integração de aplicações, é importante ter em mente os
princípios orientadores discutidos anteriormente.
No caso de bancos de dados compartilhados, por exemplo, alterações feitas por um aplicativo
podem afetar diretamente outros aplicativos que estão usando as mesmas tabelas de banco de
dados. Ambas as aplicações são fortemente acopladas.
Você pode querer evitar isso nos casos em que tenha regras adicionais a serem aplicadas
antes de aceitar as alterações no outro aplicativo.

Da mesma forma, você deve pensar sobre todos esses princípios orientadores antes de
finalizar formas de integração entre suas aplicações.
Um sistema de mensagens atua como um componente de integração entre vários aplicativos.Um sistema orientado a eventos normalmente consiste em:
EMISSORES (OU AGENTES)
Os emissores têm a responsabilidade de detectar, reunir e transferir eventos. Um emissor de
evento não conhece os consumidores, nem mesmo sabe se existe ou não um consumidor e,
caso exista, não sabe como o evento será utilizado ou processado.
CONSUMIDORES (OU COLETORES)
Os coletores têm a responsabilidade de aplicar uma reação assim que um evento seja
apresentado. A reação pode ou não ser totalmente fornecida pelo próprio coletor. Por exemplo,
o coletor pode ter apenas a responsabilidade de filtrar, transformar e encaminhar o evento para
outro componente ou pode fornecer uma reação independente a tal evento.
CANAIS DE EVENTOS
Os canais de eventos são transmitidos dos emissores para consumidores. A implementação
física dos canais pode ser baseada em componentes tradicionais, como middleware orientado
à mensagem ou comunicação ponto a ponto.
Em um projeto de um sistema de integração entre aplicações, alguns fatores/princípios devem
ser colocados em mente, tais como: fracamente acoplado, definições de interfaces comuns,
latência e confiabilidade. Vamos compreender cada um deles a seguir:
FRACAMENTE ACOPLADO
A interação entre as aplicações deve garantir uma dependência mínima entre elas. Isso
garante que qualquer modificação em uma aplicação não irá afetar a outra. É diferente dos
sistemas fortemente acoplados, nos quais uma aplicação é codificada com especificações
predefinidas da outra aplicação, e qualquer mudança pode quebrar ou mudar suas
funcionalidades com outras aplicações dependentes.
PADRÕES DE INTERFACES COMUNS
Deve garantir um formato de dados comum acordado para troca de mensagens entre
aplicativos. Isso não apenas ajuda a estabelecer padrões de troca de mensagens entre
aplicativos, mas também garante que algumas das melhores práticas de troca de informações
possam ser aplicadas facilmente. Por exemplo, você pode escolher usar o formato de dados
Avro (muito usado em Big Data) para trocar mensagens. Isso pode ser definido como seu
padrão de interface comum para troca de informações.
LATÊNCIA
É o tempo que as mensagens levam para passar entre o remetente e o receptor. A maioria dos
aplicativos deseja atingir uma baixa latência como um requisito crítico. Mesmo em um modo de
comunicação assíncrono, a alta latência não é desejável, pois um atraso significativo no
recebimento de mensagens pode causar perdas significativas para qualquer organização.
CONFIABILIDADE
Garante que a indisponibilidade temporária dos aplicativos não afete as aplicações
dependentes que precisam trocar informações. Em geral, quando o aplicativo de origem envia
uma mensagem para o aplicativo remoto, este pode estar lento ou não disponível devido a
alguma falha. A comunicação de mensagens assíncronas e confiáveis garante que a fonte da
aplicação continue o seu trabalho, e garante que o aplicativo remoto retomará sua tarefa mais
tarde.
Uma vez compreendidas essas premissas iniciais, exploraremos alguns conceitos básicos de
controle de mensageria.
FILA DE MENSAGENS
Estruturas que também são referenciadas como canais. De uma forma bem simples, podemos
definir como conectores que enviam/recebem mensagens entre as aplicações de forma
oportuna e confiável.
MENSAGENS (PACOTES DE DADOS)
Uma mensagem é um pacote de dados que é transmitido por uma rede para uma fila de
mensagens.
O aplicativo remetente divide os dados em pacotes de dados menores e os envolve como uma
mensagem com informações de protocolo e cabeçalho.

Em seguida, ele o envia para a fila de mensagens.

De maneira semelhante, um aplicativo receptor recebe uma mensagem e extrai os dados do
invólucro para processá-los posteriormente.
REMETENTE (PRODUTOR)
Os aplicativos do remetente ou produtor são as fontes de dados que precisam ser enviados a
um determinado destino. Eles estabelecem conexões com pontos de extremidade da fila de
mensagens e enviam dados em pacotes de mensagens menores, que seguem padrões de
interface comuns.
 COMENTÁRIO
Dependendo do tipo de sistema em uso, os aplicativos remetentes podem decidir enviar dados
um a um ou em lote.
DESTINATÁRIO (CONSUMIDOR)
São os destinatários das mensagens enviadas pelo aplicativo remetente.
Eles extraem ou recebem, por meio de uma conexão persistente, dados de filas de
mensagens.

Em seguida, extraem dados desses pacotes de mensagens e os usam para processamento
posterior.
PROTOCOLOS DE TRANSMISSÃO DE DADOS
Determinam as regras para controle das trocas de mensagens entre aplicativos. Diferentes
sistemas de filas usam diferentes protocolos de transmissão de dados. Alguns exemplos de
tais protocolos de transmissão de dados são:
AMQP (Advance Message Queuing Protocol).
STOMP (Streaming Text Oriented Message Protocol).
MQTT (Message Queue Telemetry Protocol).
HTTP (Hypertext Transfer Protocol).
MODO DE TRANSFERÊNCIA
O modo de transferência em um sistema de mensagens pode ser entendido como a maneira
pela qual os dados são transferidos de uma aplicação de origem para a aplicação receptora.
 EXEMPLO
Modos síncrono, assíncrono e em lote.
SISTEMA DE MENSAGENS POINT-TO-POINT (PTP)
Em um modelo PTP, os produtores de mensagens são chamados de remetentes (senders) e
os consumidores, de destinatários (receivers). Eles trocam mensagens para um destino
denominado fila. Os remetentes produzem mensagens para uma fila e os destinatários
consomem mensagens dessa fila.
O que distingue esse sistema é que uma mensagem pode ser consumida por apenas um único
destinatário. Pode haver vários destinatários ouvindo na fila da mesma mensagem, mas
apenas um deles a receberá.
 Sistema de mensageria PTP.
 COMENTÁRIO
Note que também pode haver vários remetentes, que enviarão mensagens para a fila, mas
estas serão recebidas por apenas um destinatário.
Os remetentes podem compartilhar uma conexão ou usar conexões diferentes, mas todos
podem acessar a mesma fila. Assim, mais de um destinatário pode consumir mensagens de
uma fila, mas cada mensagem pode ser consumida por apenas um destinatário.
Observe na imagem a seguir, que as mensagens 1 e 2 são consumidas por diferentes
destinatários. Como no caso dos remetentes, os destinatários também podem compartilhar
uma conexão ou usar conexões diferentes, assim como todos podem acessar a mesma fila.
 Sistema de mensageria PTP múltiplos remetentes/destinatários.
 ATENÇÃO
Remetentes e destinatários não têm dependência de tempo; o destinatário pode consumir uma
mensagem, independentemente de estar em execução quando o remetente produziu e enviou
a mensagem.
As mensagens são colocadas em uma fila na ordem em que são produzidas, mas a ordem em
que são consumidas depende de fatores como data de vencimento ou prioridade da mesma, se
um seletor é usado no consumo de mensagens e a taxa relativa de processamento da
mensagem consumidora. Os remetentes e destinatários podem ser adicionados e/ou excluídos
dinamicamente no tempo de execução, permitindo que o sistema se expanda ou contraia
conforme necessário.
SISTEMA DE MENSAGENS PUBLISHER/SUBSCRIBE
(PUB/SUB)
Neste tipo de modelo, um assinante (subscribe) registra seu interesse em um determinado
tópico ou evento e é posteriormente notificado sobre este de forma assíncrona. Os assinantes
são subsequentemente notificados de qualquer evento gerado por um editor (publisher) que
corresponda ao seu interesse registrado. Esses eventos são gerados pelos editores. 
Um modelo de mensagem Pub/Sub é usado quando você precisa transmitir um evento ou
mensagem para muitos consumidores. Ou seja, nesse modelo todos os assinantes que
estiverem ouvindo o tópico receberão a mensagem. Além disso, estas podem ser retidas no
tópico até que sejam entregues aos assinantes ativos. O ponto importante aqui é que vários
assinantes podem consumir a mensagem.
 Sistema de mensageria Pub/Sub.
Podemos resumir o fluxo do sistema Pub/Sub da seguinte forma:
As mensagens são compartilhadas por meiode um canal chamado tópico. Um tópico é um
local centralizado onde os editores (publishers) podem publicar e os assinantes (subscribers)
podem consumir mensagens. Cada mensagem é entregue a um ou mais consumidores,
chamados assinantes.
O editor geralmente não sabe e não tem conhecimento de quais assinantes estão recebendo
as mensagens de tópico. Estas são enviadas aos assinantes/consumidores sem a necessidade
de terem sido solicitadas.
As mensagens entregues a um tópico são enviadas automaticamente a todos os consumidores
qualificados. Não há acoplamento dos editores com os assinantes. Ambos, assinantes e
editores, podem ser adicionados dinamicamente em tempo de execução, o que permite ao
sistema aumentar ou diminuir a complexidade ao longo do tempo.
Todo consumidor que assina um tópico recebe sua própria cópia das mensagens publicadas
nesse tópico. Uma única mensagem produzida por um editor pode ser copiada e distribuída
para centenas ou mesmo milhares de assinantes.
 Representação gráfica do modelo Pub/Sub.
Qualquer situação na qual seja necessário notificar vários consumidores de um evento é um
bom uso do modelo Pub/Sub.
 EXEMPLO
Você deseja enviar uma notificação a um tópico sempre que ocorrer uma exceção em seu
aplicativo ou componente do sistema. Você pode não saber como essas informações serão
usadas ou que tipos de componentes as usarão. O editor/transmissor não se importará ou
precisará se preocupar com a forma como as informações serão usadas, ele simplesmente
publica em um tópico. Essa é a magia de um sistema fracamente acoplado.
PROTOCOLO DE ENFILEIRAMENTO DE MENSAGENS
AVANÇADO (AQMP)
Como abordado no início deste tópico, existem diferentes tipos de protocolos de transmissão
que definem como os dados irão fluir entre transmissores, receptores e filas de mensagens.
Vamos, então, abordar um protocolo que se faz presente em produtos concentrados em Big
Data, como o Apache Kafka: o AQMP (Advanced Queue Messaging Protocol).
AQMP é um protocolo aberto para enfileiramento de mensagens assíncronas que se
desenvolveu e amadureceu ao longo de vários anos. AQMP fornece um conjunto completo de
funcionalidades de mensagens que pode ser usado para suportar cenários de mensagens bem
avançados.
Conforme ilustrado na imagem a seguir, existem três componentes principais em qualquer
sistema de mensagens baseado em AQMP:
 Arquitetura do AQMP.
Nessa arquitetura, os produtores enviam mensagens aos brokers (corretores), que, por sua
vez, as entregam aos consumidores.
 COMENTÁRIO
Cada broker possui um componente chamado exchange (intercâmbio) que é responsável por
rotear as mensagens dos produtores para as filas de mensagens apropriadas.
Cada componente pode ser multiplicado, aumentando, assim, a escalabilidade da solução.
Produtores e consumidores comunicam-se entre si por meio de fila de mensagens associadas
aos brokers. O protocolo AQMP provê confiabilidade e garantia na entrega de mensagens
ordenadas.
Vimos aqui a importância dos sistemas de mensageria em sistemas distribuídos, que, em
nossa tecnologia contemporânea, têm sido muito usados, principalmente no gerenciamento de
mensagens de produtos de Big Data.
OS PARADIGMAS DE COMUNICAÇÃO EM
COMPUTAÇÃO DISTRIBUÍDA
Para finalizar este módulo, assista ao vídeo a seguir. Nele, abordamos os paradigmas para
comunicação em computação distribuída.
VERIFICANDO O APRENDIZADO
MÓDULO 2
 Identificar os modelos utilizados para coordenação de tempo e para garantia da
transparência de localização
NOMEAÇÃO
Nomes desempenham papel importante em todos os sistemas de computação, e são
obviamente fundamentais em sistemas distribuídos, sendo amplamente usados para, por
exemplo, compartilhar recursos e identificar entidades.
 ATENÇÃO
Nomes simples são bons para as máquinas, mas não são convenientes para seres humanos.
Então, para resolver isso, é necessário implementar um sistema de resolução de nomes por
meio de uma nomeação estruturada.
Em um sistema distribuído, essa implementação costuma ser partilhada por várias máquinas, e
a forma como a distribuição é feita influencia diretamente na escalabilidade e eficiência do
sistema. Os nomes responsáveis pela identificação unívoca de entidades e localizações
desempenham um importante papel nos sistemas computacionais.
Chamamos de entidade qualquer coisa, como discos, hosts, impressoras, arquivos, dentre
outros. Todos são acessados por meio de um ponto, basicamente um endereço associado −
como um servidor e seu endereço IP −, e saber o endereço IP não torna o processo amigável.
Essa entidade pode oferecer mais de um ponto de acesso, e o mesmo pode mudar ao longo do
tempo.
 COMENTÁRIO
Os nomes são organizados no conceito de espaço de nomes. Um nome independente de sua
localização significa que o nome da entidade é independente do seu endereço. Ou seja, nada
poderá ser referenciado sobre o endereço da entidade associada.
Vamos averiguar os seguintes exemplos a seguir:
http://www.xyz.org/index.html
http://www.xyz.br/index.html
Podemos concluir que os dois exemplos são independentes de localização, mas, como
podemos observar, o primeiro não referencia de forma consistente a localização da entidade.
Caso um sistema distribuído seja reorganizado e servidores mudem de endereço, é importante
que seja possível continuar a acessar o serviço de forma totalmente transparente. E, para isso,
devemos adotar um nome designado para ser usado por seres humanos, constituídos por
cadeias de caracteres de fácil entendimento, denominado “nome amigável”.
Um identificador é um nome que reconhece uma entidade e possui as seguintes
propriedades:
1
Um identificador referencia, no máximo, uma entidade.
Cada entidade é referenciada por, no máximo, um identificador.
2
3
Um identificador sempre referencia a mesma entidade, isto é, nunca é reutilizado.
O uso de identificadores permite que entidades sejam referenciadas sem ambiguidade.
4
.
Podemos citar como exemplos de identificadores:
ISBN de livros.
Endereço MAC ethernet.
Matrícula de um funcionário.
Código de identificação de produtos.
Um sistema de nomeação mantém uma vinculação nome-endereço, que, em sua forma mais
simples, é apenas uma tabela de pares (nome, endereço).
Em sistemas distribuídos de grande escala, é necessário usar tabelas descentralizadas, assim
como funciona o Domain Name Service (serviço de nomes de domínio), no qual um nome é
decomposto em várias partes e a resolução é feita por meio de consultas recursivas das
partes.
Assim, o servidor referenciado como nome totalmente qualificado xyz.com.br tem a seguinte
árvore, com seus respectivos serviços de nome associados e distribuídos:
. (ns) 
br (ns) 
com (ns) 
xyz (ns) 
Em uma nomeação hierárquica estruturada como o serviço de resolução de nomes (DNS),
podemos resolver os nomes de duas maneiras.
FORMA ITERATIVA
javascript:void(0)
O servidor responderá somente com o que sabe: o nome do próximo servidor a ser buscado, e
o cliente procura iterativamente os outros servidores.
FORMA RECURSIVA
O servidor passa o resultado para o próximo servidor que encontrar, e ao cliente restará
somente receber a mensagem de endereço de nome encontrado ou não encontrado.
A principal desvantagem no método recursivo é uma maior exigência de desempenho aos
servidores de nomes, pois estes precisam manipular a resolução completa de um caminho.
No entanto, tem duas grandes vantagens:
Armazenar temporariamente os resultados intermediários, diminuindo o custo de
comunicação.
Cada servidor de nomes no caminho aprende gradativamente o endereço de cada
servidor de nomes de nível mais baixo.
O DNS é, portanto, o sistema de resolução de nomes da Internet e base para grande parte dos
sistemas distribuídos.
De forma hierárquica, ele gera uma base de dados distribuída que permite localizar
rapidamente qualquer entidade mapeada para a Internet ou dentro de uma rede local em uma
visão de intranet.
Sua implementação é feita no que chamamosde zonas, que não se sobrepõem. Cada zona é
construída em um servidor de nomes, que possui autoridade por um pedaço do espaço de
nome por ele gerenciado e pode ser replicado, garantindo maior disponibilidade nos serviços
prestados.
javascript:void(0)
 Resolução de nomes.
NOMEAÇÃO EM ATRIBUTO
Nesta abordagem, são fornecidas descrições a partir de termos de pares (atributo, valor),
conhecidos como serviços de diretórios. Com estes, as entidades têm um conjunto de atributos
associados que podem ser usados para procurá-las.
Consultar valores em um sistema de nomeação baseado em atributos requer uma busca
exaustiva nos descritores, o que pode acarretar sérios problemas de desempenho e
escalabilidade. Como exemplo prático, temos implementações hierárquicas − como o LDAP
(Protocolo Leve de Acesso a Diretório) − e implementações descentralizadas − as tabelas
Hash distribuídas (DHT).
A abordagem de implementações hierárquicas combina nomeação estruturada com nomeação
baseada em atributos amplamente adotada, como no Active Directory da Microsoft ou no
ambiente de código aberto com o OpenLDAP.
O serviço de diretório LDAP é organizado na forma de registros, conhecidos como entrada de
diretório. Nela, cada registro é composto por um conjunto de pares (atributo, valor), nos quais
cada atributo possui um tipo associado e os valores podem ter valor único ou serem
multivalorados.
 EXEMPLO
Os atributos: C = Country (País), O = Organization (Organização), OU = Organization Unit
(Unidade Organizacional).
Populamos, então, com os atributos:
uid=professor, C=br, O=xyz, OU=centro.
A implementação de um serviço de diretório LDAP é semelhante à implementação do serviço
de nomeação do DNS. Diretórios de grande escala particionam as árvores de diretório,
distribuindo-as em vários servidores, equivalentes às zonas em DNS, mas com a diferença de
suportar um número bem maior de valores para consultas, predefinidas em uma estrutura
denominada esquemas.
DISTRIBUTED HASH TABLES (DHT)
As tabelas de Hash distribuídos são uma classe de sistemas distribuídos descentralizados que
provém um serviço de busca parecida com a de uma tabela de Hash: pares (atributo, valor) são
armazenados no DHT, e qualquer nó pode recuperar o valor associado a cada atributo.
A responsabilidade por manter o mapa dos atributos até os valores é distribuída entre os nós
de modo que uma mudança no grupo de nós cause uma interrupção mínima. Isso permite que
o DHT funcione para uma grande escala de nós, com estes chegando, saindo e falhando
continuamente, possibilitando uma busca eficaz, em vez de executar uma busca excessiva por
toda a extensão do espaço de atributos.
 COMENTÁRIO
Um problema do DHT, porém, é que ele só realiza buscas pelo nome exato do arquivo, no lugar
de usar palavras-chave. Essa solução é muito utilizada para nomeação em sistemas peer-to-
peer.
Nesse ambiente, existe uma descentralização, não há uma entidade de coordenação central. O
ambiente é altamente escalável, estando preparado para funcionar corretamente tanto com
milhares de nós como para milhões de nós sem que haja uma queda na qualidade do serviço
oferecido. É totalmente tolerante a falhas, e a entrada, a saída e a falha de peers não devem
abalar o sistema, mesmo que essas ocorram frequentemente.
Para prover essas características, uma técnica usada é fazer com que cada nó coordene
apenas uma quantidade pequena de outros nós, quando comparada com a quantidade total na
DHT.
Dessa forma, é necessário realizar somente uma quantidade limitada de trabalho para cada
mudança.
São exemplos dessa estrutura: BitTorrent, eMule, YaCy, dentre muitos outros.
NOMEAÇÃO
No vídeo a seguir, apresentamos um resumo do módulo explicando sobre os sistemas de
nomeação e suas técnicas.
VERIFICANDO O APRENDIZADO
MÓDULO 3
 Analisar os conceitos de consistência e replicação nos sistemas distribuídos
SINCRONIZAÇÃO DE RELÓGIOS
A sincronização entre processos locais é a base de entendimento para a comunicação entre
processos em sistemas distribuídos, de tal forma que devem responder a simples perguntas:
Como as regiões críticas são implementadas em um sistema distribuído?
Como esses recursos são alocados?
Como é gerenciado o acesso concorrente?
Como sabemos que os eventos estão de acordo com uma linha do tempo?
Em um sistema local, as regiões críticas, a exclusão mútua e demais problemas de
sincronização são resolvidos, na maioria das vezes, por métodos de semáforos e monitores.
Obviamente, porém, estes não são recomendados para serem usados em sistemas
distribuídos, porque eles “esperam” a existência de uma memória compartilhada.
Em um sistema centralizado, o tempo não é ambíguo, então, quando um determinado processo
consulta a hora, ele faz uma chamada ao sistema, que responde.
 EXEMPLO
Se um processo A perguntar a hora e, um pouco mais tarde, um processo B fizer o mesmo, o
valor obtido por B será maior do que o obtido por A.
Entretanto, em um sistema distribuído, conseguir acordo nos horários não é trivial.
RELÓGIOS LÓGICOS
Os hosts possuem circuitos internos para gerenciar um relógio físico interno, composto por um
cristal de quartzo que, sob pressão, oscila em uma frequência definida, gerando o correto
batimento para o funcionamento do ambiente.
 COMENTÁRIO
Todos os processos da máquina utilizam esse mesmo relógio, mas, com diversos servidores
espalhados, é praticamente impossível manter esses cristais funcionando na mesma
frequência.
Esses relógios podem ficar defasados uns com os outros, mas como sincronizá-los para
que estejam de acordo com o tempo real?
Para isso, Cristian (1989) propôs usar um modelo cliente/servidor no qual clientes consultam a
hora em um servidor de tempo de precisão. Em sistemas distribuídos, não se pode
determinar com precisão o delay (atraso) de transmissão da rede.
 EXEMPLO
Um cliente desejando sincronizar−se com um servidor envia−lhe uma requisição, e este
responde devolvendo o tempo atual de seu clock. O problema é que, quando o cliente recebe
de volta a mensagem, o tempo retornado já está desatualizado.
É claro que a latência de uma Wan poderá fornecer uma hora desatualizada. Criou-se, então, o
protocolo de tempo de rede (NTP – Network Time Protocol), que se ajusta em um modelo de
pares de servidores no qual um consulta o outro para entregar uma maior precisão de relógio.
Normalmente, um relógio de referência atômico, stratum 0, entregará essa precisão.
COMO FUNCIONA O PROTOCOLO NTP?
O servidor NTP consulta seus pares de tempo em tempo, perguntando a hora que cada um
está registrando, e, com base nos valores retornados, calcula um horário médio e informa para
todos os participantes adiantar ou atrasar seus relógios.
Lamport (1978) mostrou que, embora a sincronização de relógios seja possível, não precisa ser
absoluta, pois, se dois processos não interagem entre si, seus relógios não precisam ser
sincronizados. Assim, não é necessário que todos os processos concordem com a hora exata,
mas com a ordem em que os eventos ocorrem.
SOLUÇÕES DE PROBLEMAS DE
SINCRONIZAÇÃO EM SISTEMAS
DISTRIBUÍDOS
ALGORITMOS CENTRALIZADOS
O caminho mais rápido para atingir exclusão mútua em sistemas distribuídos é similar ao feito
em sistema com um único processador.
Um processo é eleito como o coordenador (por exemplo, um rodando na máquina com o
maior endereço da rede ou escolhido por algum algoritmo de eleição).
Sempre que um processo quiser entrar na região crítica, ele envia uma requisição de
mensagem para o coordenador, declarando qual região ele quer entrar e requisitando
permissão.
Se nenhum outro processo estiver atualmente naquela região crítica, o coordenador envia uma
resposta de volta, dando permissão.
Quando a resposta chega, o processo de requisição entra na região crítica.
Quando o processo 1 sai dessa região, ele envia uma mensagem para o coordenador,
liberando o acesso exclusivo.
O coordenador toma as requisições da fila e envia ao processo uma mensagemde permissão.
Se o processo ainda estiver bloqueado (por exemplo, é a primeira mensagem para ele), ele é
desbloqueado e entra na região crítica.
Esse algoritmo garante exclusão mútua: o coordenador somente deixa um processo, por
tempo, entrar na região crítica. É também justo, já que as concessões às requisições são feitas
na ordem na qual são recebidas. Nenhum processo espera para sempre. Além disso, o
esquema é fácil de implementar, e requer somente três mensagens para uso da região crítica
(requisição, concessão, liberação). Pode também ser usado para alocação mais geral dos
recursos, em vez de somente gerenciar regiões críticas.
 COMENTÁRIO
A abordagem centralizada também possui problemas. O coordenador é um único ponto de
falha, logo, se quebrar, todo o sistema pode parar. Se os processos normalmente bloquearem
depois de uma requisição, eles não poderão distinguir um coordenador "morto" de uma
"permissão negada", já que em ambos nenhuma mensagem retorna. Em adição, em sistemas
grandes, um único coordenador pode tornar um gargalo da performance.
ALGORITMOS DISTRIBUÍDOS
Como ter um único ponto de falha é um problema, os pesquisadores têm estudado alguns
algoritmos para garantir a exclusão mútua distribuída. O algoritmo de Ricart e Agrawala requer
que haja uma ordem total de todos os eventos no sistema. Ou seja, para qualquer par de
eventos, como mensagens, não deve haver ambiguidade sobre qual é o primeiro.
O algoritmo de Lamport, apresentado anteriormente, é um modo de alcançar essa ordenação,
e pode ser usado para fornecer um timestamp para a exclusão mútua distribuída.
Quando um processo quer entrar na região crítica, ele constrói uma mensagem contendo o
nome da região de interesse, o número do processo e o tempo corrente.
Envia, então, a mensagem para todos os outros processos, conceitualmente incluindo-o
também.
Um grupo confiável de comunicação, se disponível, pode substituir as mensagens individuais
(multicast em vez de unicast).
Quando um processo recebe uma requisição de outro, a ação que se executa depende do seu
estado com relação à região crítica nomeada na mensagem.
Três casos devem ser distinguidos:
Se o receptor não é a região crítica e não quer entrar nesta, ele retorna um OK (ACK)
para o transmissor.
Se o receptor estiver na região crítica, ele não responderá. Simplesmente enfileira a
requisição.
Se o receptor quiser entrar na região crítica, mas ainda não o fez, ele compara o
timestamp da mensagem com o conteúdo da mensagem que enviou para todos. A menor
ganha. Se a mensagem que chegar tiver menor prioridade, o receptor envia uma
mensagem de OK. Se sua própria mensagem tiver um timestamp inferior, o receptor
enfileira a mensagem e não enviará nada.
Depois de enviar as requisições pedindo a devida autorização para entrar na região crítica, um
processo vai esperar até que todos tenham dado as devidas permissões. Assim que todas as
permissões cheguem, ele poderá entrar na região crítica. Quando sair da região crítica, enviará
um OK para todos os processos da sua fila e os deletará de sua fila. Se não houver um conflito,
o mesmo funcionará como esperado.
ALGORITMO EM ANEL
Veremos, agora, uma abordagem totalmente diferente para obter a exclusão mútua em um
sistema distribuído.
Um anel circular é construído, e cada processo estará associado a uma posição no anel.
Essas posições podem ser alocadas em uma ordem numérica dos endereços da rede ou de
qualquer outra forma.
Não importa qual é a ordem, importa somente que cada processo saiba que é o próximo dentro
do anel.
Quando o anel é inicializado, o processo recebe um token, que circula pelo anel. Quando um
processo adquire o token do processo vizinho, é feita uma verificação para entrar na região
crítica. Se precisar, o processo entra na região crítica, efetua o trabalho a ser feito e libera a
região. Depois de sair, ele passa o token para o controle do anel. Não é permitido entrar em
uma segunda região crítica usando o mesmo token.
Se o processo recebe o token de seu vizinho e não está interessado em entrar na região
crítica, ele somente o passa adiante. Como consequência, quando nenhum processo quer
entrar em nenhuma região crítica, o token somente circula em alta velocidade dentro do anel.
Desse modo, somente um processo que possuir o token poderá estar na região crítica.
Visto que o token circula pelos processos em uma ordem bem definida, um starvation não
poderá ocorrer. Uma vez que um processo decide entrar na região crítica, no pior caso, ele terá
que esperar que todos os outros processos entrem na região crítica e liberem−na.
 COMENTÁRIO
Como grande desvantagem, se o token for perdido, o mesmo deverá ser gerado novamente.
Mas detectar essa perda é difícil, visto que o tempo entre sucessivas aparições do token da
rede não é limitado. O fato de ele não ter sido avistado por uma hora não significa que ele foi
perdido; alguém pode ainda estar utilizando-o.
O algoritmo também tem problemas no caso de uma falha do processo, mas a sua
recuperação é mais fácil do que nas outras situações. Um processo, ao passar o token para
seu vizinho, pode perceber que ele está fora do ar. Nesse ponto, o processo fora do ar poderá
ser removido do grupo, e o que possui o token enviará para o próximo processo, no anel. Para
que isso aconteça, se faz necessário que todos mantenham a configuração atual do anel.
ALGORITMO DE DETECÇÃO DE DEADLOCKS
DISTRIBUÍDOS
Um deadlock (impasse) é causado por uma situação na qual um conjunto de processos estará
bloqueado permanentemente. Isto é, não conseguem prosseguir com sua execução,
esperando um evento externo promovido por outro processo para sair da situação de bloqueio
permanente.
Um deadlock ocorre devido aos processos de alocação formarem um ciclo, no qual vários
processos tentam alocar recursos para realizar suas tarefas, mas foram bloqueados por outros,
que não liberam os seus para deixar que os outros sigam suas execuções. Assim, não terão
todos os recursos disponíveis por não terem terminado suas tarefas.
Algumas estratégias são adotadas para o tratamento de deadlock, evitando a ocorrência por
meio de uma alocação de cuidados dos recursos, prevenindo de forma estática que os
deadlocks não ocorram e permitindo que o deadlock ocorra, entretanto, que seja possível
detectar e tentar se recuperar da situação. Podemos usar um algoritmo centralizado, que
examina o estado do sistema para determinar se existe um deadlock e, nesse caso, executa
uma ação corretiva.
Em sistemas distribuídos, coletar e colecionar essas informações sobre necessidade/oferta de
recursos é extremamente difícil e complicado de gerenciar. Mas é possível manter essas
informações sobre alocação de recursos, criando um grafo de alocação de recursos e
procurando ciclos nesse grafo.
 ATENÇÃO
Caso o coordenador detecte um ciclo, ele matará um dos processos, acabando com o
deadlock.
ALGORITMO DE ELEIÇÃO
Em sistemas distribuídos, diversos algoritmos necessitam que um processo exerça uma função
especial, como coordenador, inicializador ou sequenciador. São exemplos:
Coordenador de exclusão mútua com controle centralizado.
Coordenador para detecção de deadlock distribuído.
Sequenciador de eventos para ordenação consistente centralizada.
A falha do coordenador compromete o serviço para vários processos, portanto, um novo
coordenador deve assumir. Para isso, fazemos uma eleição. O objetivo é eleger um processo,
entre os ativos, para desempenhar função especial. Para tal, existem algoritmos especiais para
escolha de um processo que assumirá a posição de coordenador. Podemos citar os algoritmos
bully (valentão) e o ring (anel).
Para esses algoritmos, assume-se as seguintes premissas:
Todo o processo no sistema tem prioridade única.
Quando a eleição acontece, o processo com maior prioridade entre os processos ativos é
eleito como coordenador.
Na recuperação (volta à atividade), um processo falho pode tomar ações para juntar-se aogrupo de processos ativos.
ALGORITMO DE BULLY
Quando um processo nota que o coordenador não responde, uma eleição deverá ser iniciada.
1
Esse processo P envia uma mensagem de eleição para todos os processos com número maior
que o seu.
Se não houver nenhuma resposta, P ganha a eleição e se torna o coordenador.
2
3
Se alguém responde, a tarefa de P estará finalizada, e a eleição poderá continuar a partir dos
processos com maior número.
Quando a eleição finaliza, o vencedor enviará uma mensagem para todos os processos
informando que será o novo coordenador.
ALGORITMO DE RING (ANEL)
Nesse algoritmo, os processos podem ser fisicamente ou logicamente ordenados. Eles sabem
que são seus sucessores e, quando um processo nota que o coordenador não responde,
constrói uma mensagem de eleição, inclui seu número e envia para seu sucessor. A
mensagem circula pelo anel, e cada processo inclui seu número iniciador da eleição, recebe a
mensagem de volta e determina quem é o coordenador baseado em qual processo possui o
maior número. Após isso, envia a mensagem coordenador pelo anel.
SINCRONIZAÇÃO EM SISTEMAS
DISTRIBUÍDOS
Os algoritmos para sincronização em sistemas distribuídos são apresentados no vídeo a
seguir.
REPLICAÇÃO
Em nosso dia a dia, o dado, que é considerado o novo petróleo por muitos especialistas, é um
ativo fundamental para tomadas de decisões de empresas e instituições. Portanto, ter os dados
replicados é de suma importância, pois garante suas duas razões principais: confiabilidade e
desempenho. Além disso, de forma embutida, garante a disponibilidade desses dados.
Seguindo o conceito da disponibilidade, conseguimos manter os sistemas operacionais
mesmo na queda de uma das réplicas, pois pode-se redirecionar o acesso a qualquer uma das
suas cópias disponíveis, além de garantir uma melhor proteção contra dados corrompidos. O
desempenho também pode ser alcançado, uma vez que é possível ter várias cópias
espalhadas, e um redirecionamento de acesso pode permitir que a carga de trabalho seja
balanceada entre suas várias réplicas.
 COMENTÁRIO
Ter muitas cópias pode levar a problemas de consistência. Sempre que houver uma
atualização, será necessário modificar todas as demais cópias para garantir a consistência dos
dados, sendo esse o maior problema em projetos que envolvam esses conceitos.
Usar uma memória local temporária para os dados acelera o desempenho e entrega um melhor
“sentimento” de velocidade aos seus utilizadores, mas gera sempre uma informação
desatualizada. Chegamos novamente, então, ao impasse desempenho versus escalabilidade,
em que manter várias cópias pode significar problemas de escalabilidade.
Assim, como um conjunto de cópias consistentes são sempre iguais, uma operação de leitura
sempre retornará o mesmo valor. Mas, em uma operação de escrita, a atualização será
propagada para todas as cópias antes que ocorra outra operação subsequente,
independentemente de em qual réplica for realizada a operação.
Chamamos de replicação síncrona ou consistência estrita a existência de uma atualização
realizada de forma atômica. No entanto, teremos dificuldade em garantir que todas as réplicas
estejam sincronizadas globalmente. Isso porque implementar métodos síncronos pode ser
muito caro, na visão do desempenho distribuído, para as aplicações sensíveis a latência.
 COMENTÁRIO
Em muitos casos, então, a melhor solução poderá ser o relaxamento das restrições de
consistência estrita, ganhando maior desempenho em situações nas quais a estratégia a ser
seguida seria que as cópias nem sempre seriam iguais em todos os lugares. Dessa forma, as
aplicações deveriam estar preparadas para receber informações um pouco desatualizadas.
Atualmente, cada vez mais vemos soluções de banco de dados NoSQL, bancos de dados
distribuídos e replicados de alta escala — nos quais estão sendo aplicados os conceitos de
consistência eventual, pois nem sempre podemos fazer uso de uma consistência estrita
(síncrona) —, que toleram um alto grau de inconsistência. Isso porque existem situações que
executam muito mais operações de leitura do que de escrita.
Portanto, a questão aqui é a velocidade com que as atualizações devem ser disponibilizadas
para os processos que realizam somente leitura. Se nenhuma atualização ocorrer por um
tempo bastante longo, todas as réplicas ficarão gradativamente consistentes. Ou seja, na
ausência de atualizações, todas as réplicas convergem em direção a cópias idênticas umas
das outras, e temos a garantia de que as atualizações serão propagadas para todas as réplicas
no tempo que for programado para tal.
VERIFICANDO O APRENDIZADO
MÓDULO 4
 Identificar as principais interfaces de desenvolvimento de códigos para sistemas
paralelos e distribuídos
INTERFACES DE PROGRAMAÇÃO
DISTRIBUÍDA
Com a explosão da Internet, as aplicações tiveram que se adaptar a uma nova realidade de
comunicação. Surgiu a necessidade de que elas parassem de executar em um ambiente
fechado e proporcionassem uma distribuição de conteúdo entre elas mesmas. Para
proporcionar que essa distribuição ou comunicação ocorra entre as aplicações, estas devem
ser compostas em camadas, ou seja, devem se tornar aplicações distribuídas.
 COMENTÁRIO
Criar uma aplicação distribuída é uma tarefa muito importante nos dias atuais. Isso porque,
com a facilidade de acesso promovida com a difusão da Internet, as aplicações precisam, além
de se comunicarem entre si, ter a maior disponibilidade e confiabilidade possível.
Existem APIs que funcionam em sistemas operacionais para facilitar as tarefas de
comunicação e de processamento, portanto, não precisam da utilização da Internet. Neste
módulo, exploraremos algumas dessas APIs, tais como sockets, MPI, RPC e barramento, com
alguns exemplos de implementação em linguagem C.
PROGRAMAÇÃO EM SOCKETS
O termo socket refere-se à Interface de Programação de Aplicações (API) implementada pelo
grupo de distribuição de software UNIX da Universidade de Berkeley (BSD). A interface
fornecida pelo socket permite que os processos acessem serviços de rede. A troca de
mensagens é feita entre dois processos, usando vários mecanismos de transporte.
A comunicação via sockets deve ser realizada sempre dentro de um domínio, que é
caracterizado por uma abstração feita para designar os serviços de rede, uma estrutura de
endereçamento comum e protocolos entre dois pontos de comunicação em uma rede.
Dessa forma, processos que são executados em sistemas diferentes podem se comunicar por
meio dos sockets somente se utilizarem o mesmo esquema de endereçamento. Em TCPI/IP, os
sockets UDP e TCP são a interface provida pelos respectivos protocolos na interface da
camada de transporte.
 Estrutura de comunicação em sockets.
Como vimos, os sockets foram criados na forma de uma API que possibilita
aplicações/processos se comunicarem, mas quais são essas interfaces que permitem essas
comunicações? A seguir, listamos algumas das principais funções utilizadas ao criar um
programa utilizando sockets.
getaddrinfo() Traduz nomes para endereços
socket() Cria um socket e retorna o descritor de arquivo
bind() Associa o socket a um endereço socket e a uma porta
connect() Tenta estabelecer uma conexão com um socket
listen() Coloca o socket para aguardar conexões
accept() Aceita uma nova conexão e cria um socket
send() Caso conectado, transmite mensagens ao socket
recv() Recebe as mensagens por meio do socket
close() Desaloca o descritor de arquivo
shutdown() Desabilita a comunicação do socket
Quando se programa utilizando sockets, uma arquitetura muito empregada é a
cliente/servidor (client/server). Para isso, temos que implementar um programa cliente e um
programa servidor. Ambos fazem uso da mesma API de sockets.
Os sockets UDP são basicamente canais não confiáveis, pois:
Não garantem entrega dos datagramas;
Podem entregar datagramas duplicados;
Não garantem ordem de entrega dos datagramas;
Não têm estado de conexão (escuta,estabelecida).
Para programar em sockets UDP, utilizamos as seguintes funções:
Para criar o socket.
DatagramSocket s = new DatagramSocket(6789);
Para receber um datagrama.
receive(req)
Para enviar um datagrama.
send(resp);
Para fechar um socket.
close();
Para montar um datagrama para receber mensagem.
new DatagramPacket(buffer, buffer.length);
Para montar um datagrama para ser enviado.
new DatagramPacket(msg, msg.length, inet, porta);
Buffer e msg são byte[ ].
Como podemos observar, nesse modelo o send não é bloqueante, mas o receive é, a menos
que possamos especificar um timeout. Em uma visão multithreads, teremos um thread principal
que receberá as requisições e outras threads servindo aos respectivos clientes.
Dentro da camada de transporte da pilha TCP/IP, temos o protocolo TCP, que implementa uma
comunicação confiável.
Na visão do desenvolvedor, é visto como um fluxo contínuo (stream), e seus dados são
tratados como segmentos. Existe total garantia de entrega, ordenamento das mensagens e não
ocorrência de duplicação. Estabelece uma conexão e, portanto, controla o estado da conexão
(estabelece, escuta e fecha sessões). Possui, ainda, outras características integradas, como
sessão ponto a ponto (um transmissor: um receptor), ou seja, sockets conectados e controle de
congestionamento e de fluxo, para que o transmissor não sobrecarregue o receptor.
Ainda dentro desse contexto, um stream é tratado como uma sequência de bytes transmitida e
recebida continuamente por um processo. O TCP preocupa-se em segmentar o stream e, se
necessário, entregar os segmentos à aplicação na ordem correta. Do ponto de vista do
desenvolvedor de sockets TCP, basta gravar os dados em um buffer de saída para que sejam
enviados e ler os dados de chegada em um buffer de entrada.
Para programar em sockets TCP, utilizamos as seguintes funções:
Servidor cria socket de escuta em uma porta (Ex.: 6789).
ServerSocket ss = new ServerSocket(6789);
Servidor aceita uma conexão e cria um novo socket para atendê-la.
Socket a = ss.accept();
Cliente cria socket de conexão.
Socket s = new Socket(“localhost”, 6789);
Fecha o socket.
close();
LADO CLIENTE
O programa cliente primeiro cria um socket por meio da função "socket()". Em seguida,
conecta-se ao servidor por meio da função "connect()" e inicia um loop (laço), que fica fazendo
"send()" (envio) e "recv()" (recebimento) com as mensagens específicas da aplicação. É no par
send/recv que temos a comunicação lógica. Quando alguma mensagem da aplicação diz que é
o momento de terminar a conexão, o programa chama a função "close()" para finalizar o
socket.
LADO SERVIDOR
O programa servidor também utiliza a mesma API de sockets. Ou seja, inicialmente ele
também cria um socket. No entanto, diferentemente do cliente, o servidor precisa fazer um
"bind()", que associa o socket a uma porta do sistema operacional, e depois utilizar o "listen()"
para escutar novas conexões de clientes nessa porta.
Quando um novo cliente faz uma nova conexão, a chamada "accept()" é utilizada para começar
a se comunicar. Da mesma forma que no cliente, o servidor fica em um loop, recebendo e
enviando mensagens por meio do par de funções "send()" e "recv()".
Quando a comunicação com o cliente termina, o servidor volta a aguardar novas conexões de
clientes.
 Estrutura de sockets cliente/servidor.
EXEMPLO DE IMPLEMENTAÇÃO DE UM CÓDIGO PARA
O LADO CLIENTE
A seguir, apresentamos um exemplo de código em Python utilizando socket para o lado
servidor.
from socket import * 
s = socket(AF_INET, SOCK_STREAM) 
s.bind ((HOST, PORT)) 
s.listen(1) 
(conn, addr) = s.accept # Retorna novo socket e endereço 
client 
while True: 
 data = conn,recv(1024) # Recebe os dados do cliente 
 if not data: break # Para se o cliente para 
 conn.send(str(data)+”*” # Retorna enviando os dados + um “*” 
conn.close() # Fecha a conexão
Exemplo de um código em C para o lado servidor de um socket para ambiente Linux:
#include <stdio.h> 
#include <stdlib.h> 
#include <sys/socket.h> 
#include <arpa/inet.h> 
 
#define NRCON 5 /* Numero de conexoes */ 
#define BUFFSIZE 32 
#define PORTA 5001 
 
void TrataCliente(int sock) { 
 char buffer[BUFFSIZE]; 
 int recebido = -1; 
 /* Receive message */ 
 if ((recebido = read(sock, buffer, BUFFSIZE)) < 0) { 
 perror("erro no recebimento dos dados"); 
 exit(-1); 
 } 
 printf("Dados recebidos %s \n", buffer); 
 /* Send bytes and check for more incoming data in loop */ 
 while (recebido > 0) { 
 /* Send back received data */ 
 if (write(sock, buffer, recebido, 0) != recebido) { 
 perror("erro no envio dos dados"); 
 exit(-1); 
 } 
 /* Check for more data */
 if ((recebido = read(sock, buffer, BUFFSIZE)) < 0) { 
 perror("erro no recebimento dos dados"); 
 exit(-1); 
 } 
 printf("Dados recebidos %s \n", buffer); 
 } 
 close(sock); 
} 
 
int main(void) { 
 int ssocket, csocket; 
 struct sockaddr_in end_servidor, end_cliente; 
 
 if ((ssocket = socket(PF_INET, SOCK_STREAM, 0)) < 0) { 
 perror("Erro criação socket"); 
 exit(-1); 
 } 
 
 /* Construct the server sockaddr_in structure */ 
 memset(&end_servidor, 0, sizeof(end_servidor)); /* Clear struct */ 
 end_servidor.sin_family = AF_INET; /* Internet/IP */ 
 end_servidor.sin_addr.s_addr = htonl(INADDR_ANY); /* Incoming addr */ 
 end_servidor.sin_port = htons(PORTA); /* server port */ 
 /* Bind the server socket */ 
 if (bind(ssocket, (struct sockaddr *) &end_servidor, sizeof(end_servidor)) 
 < 0) { 
 perror("erro bind"); 
 exit(-1); 
 } 
 
 /* Listen on the server socket */ 
 if (listen(ssocket, NRCON) < 0) { 
 perror("erro listen"); 
 exit(-1); 
 } 
 /* Run until cancelled */ 
 while (1) { 
 unsigned int clientlen = sizeof(end_cliente); 
 /* Wait for client connection */ 
 if ((csocket = accept(ssocket, (struct sockaddr *) &end_cliente, 
 &clientlen)) < 0) { 
 perror("erro accept"); 
 exit(-1); 
 } 
 fprintf(stdout, "Cliente conectado: %s\n", 
 inet_ntoa(end_cliente.sin_addr)); 
 TrataCliente(csocket); 
 } 
 
 exit(0); 
} 
Exemplo de implementação de um código para o lado cliente
A seguir, apresentamos um exemplo de código em Python utilizando socket para o lado
servidor:
from socket import * 
s = socket(AF_INET, SOCK_STREAM) 
s.connect((HOST, PORT)) # conecta ao servidor (bloqueia até aceitar) 
s.send('Ola, Mundo') # enviando os dados 
data = s.recv(1024) # recebe a resposta 
print data # mostra o resultado 
s.close() # fecha a sessão
Exemplo de um código em C para o lado cliente de um socket para ambientes Linux:
#include <stdio.h> 
#include <sys/socket.h> 
#include <arpa/inet.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h> 
#include <netinet/in.h> 
 
#define BUFFSIZE 32 
 
int main(int argc, char *argv[]) { 
 int csocket; 
 struct sockaddr_in end_servidor; 
 char buffer[BUFFSIZE]; 
 char mensagem[15] = "Ola Mundo"; 
 unsigned int comprimento; 
 int recebido = 0; 
 
 if (argc != 3) { 
 fprintf(stderr, "Utilize: Cliente <server_ip> <port>\n"); 
 exit(1); 
 } 
 /* Create the TCP socket */ 
 if ((csocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { 
 perror("Erro na criacao do socket"); 
 exit(-1); 
 } 
 /* Construct the server sockaddr_in structure */ 
 memset(&end_servidor, 0, sizeof(end_servidor)); /* Clear struct */ 
 end_servidor.sin_family = AF_INET; /* Internet/IP */ 
 end_servidor.sin_addr.s_addr = inet_addr(argv[1]); /* IP address */ 
 end_servidor.sin_port = htons(atoi(argv[2])); /* server port */ 
 /* Establish connection */ 
 if (connect(csocket, (struct sockaddr *) &end_servidor, sizeof(end_servidor)) < 0) { 
 perror("erro no conect"); 
 exit(-1); 
 } 
 
 /* Send the word to the server */ 
 comprimento = strlen(mensagem); 
 if (write(csocket, mensagem, comprimento) != comprimento) { 
 perror("erro no envio de dados"); 
 exit(-1);} 
 /* Receive the word back from the server */ 
 fprintf(stdout, "Recebido: "); 
 while (recebido < comprimento) { 
 int bytes = 0; 
 if ((bytes = read(csocket, buffer, BUFFSIZE - 1)) < 1) { 
 perror("erro no recebimento dos dados"); 
 exit(-1); 
 } 
 recebido += bytes; 
 buffer[bytes] = '\0'; /* Assure null terminated string */ 
 fprintf(stdout, buffer); 
 } 
 fprintf(stdout, "\n"); 
 close(csocket); 
 exit(0); 
} 
PROGRAMAÇÃO PARALELA POR
INTERFACE DE PASSAGEM DE
MENSAGENS (MPI)
O conceito de programação paralela está diretamente relacionado ao uso de computadores
paralelos − dotados de várias unidades de processamento, com capacidade de executar
programas paralelamente.
No caso de programas paralelos, temos instruções sendo executadas em diferentes
processadores ao mesmo tempo. Além disso, há geração e troca de informação entre os
processadores.
O objetivo da programação paralela é transformar grandes algoritmos complexos em pequenas
tarefas, que possam ser executadas simultaneamente por vários processadores, reduzindo,
assim, o tempo de processamento. Mas isso demanda tempo para analisar o código a ser
paralelizado, recodificar a aplicação, depurar e esperar para se encontrar a melhor solução
para o problema.
O paralelismo pode ser explorado de duas formas.
No modo implícito, existe a exploração automática do paralelismo, na qual o compilador gera
as operações paralelas. São exemplos: HPF (High Performance Fortran) e, parcialmente,
alguns compiladores da Intel.
No paralelismo explícito, é dever do programador o processo de paralelizar o seu código,
usando APIs como MPI, PVM ou diretivas com o OpenMP.
As sincronizações dos processos geram muitos problemas ao programador, pois as tarefas
executadas em paralelo aguardam a finalização mútua para, só então, coordenar os resultados
ou trocar dados e reiniciar novas tarefas.
 ATENÇÃO
É necessário que haja uma perfeita coordenação do tamanho dos processos (granulosidade) e
da comunicação entre eles para que o tráfego gerado não seja maior do que o processamento,
e que, consequentemente, haja uma queda no desempenho dos processos em execução.
O MPI é uma biblioteca com funções (API) para troca de mensagens, responsável pela
comunicação e sincronização de processos em um cluster paralelo. Dessa forma, os processos
de um programa paralelo podem ser escritos em uma linguagem de programação sequencial,
tal como C, Python ou Fortran.
O principal foco do MPI é disponibilizar uma interface que seja largamente utilizada no
desenvolvimento de programas que utilizem troca de mensagens. Além de garantir a
portabilidade dos programas paralelos, essa interface deve ser implementada eficientemente
nos diversos tipos de máquinas paralelas existentes, sejam elas supercomputadores dedicados
ou por meio de clusters de computadores do mercado.
O objetivo principal é que o MPI se torne o padrão de interface mais utilizado em sistemas
distribuídos, no qual cada processador executa um processo paralelo seguindo o modelo
SPMD (Single Program Multiple Data – Único Programa sobre Múltiplos Dados). Isso porque o
MPI define um conjunto de rotinas para facilitar a comunicação (troca de dados e
sincronização) entre processos em memória. Ele é portável para qualquer arquitetura, tem
aproximadamente 300 funções para programação e ferramentas para análise de desempenho.
 COMENTÁRIO
A biblioteca MPI possui rotinas para programas em linguagem C / C++, Python, Fortran 77/90 e
muitas outras linguagens de programação. Os programas são compilados e ligados à biblioteca
MPI. Inicialmente, na maioria das implementações, um conjunto fixo de processos é criado.
Os elementos mais importantes em implementações paralelas são:
A comunicação de dados entre processos paralelos.
O balanceamento da carga.
Os processos podem usar mecanismos de comunicação ponto a ponto em operações para
enviar mensagens de um determinado processo a outro. Um grupo de processos pode chamar
operações coletivas de comunicação para executar operações globais.
O MPI é capaz de suportar comunicação assíncrona e programação modular por meio de
mecanismos de comunicadores que permitem ao usuário MPI definir módulos que englobem
estruturas de comunicação interna. Cada processo executa e comunica-se com outras
instâncias do programa, possibilitando a execução no mesmo processador ou em diferentes
processadores. Essas instâncias podem se dar por meio de comunicação ponto a ponto ou
coletiva.
O melhor desempenho acontece quando esses processos são distribuídos entre diversos
processadores. A comunicação básica consiste em enviar e receber dados de um processador
para outro. Essa comunicação se dá por meio de uma rede de alta velocidade (como InfiniBand
ou Ethernet 10G), na qual os processos estarão em um sistema de memória distribuída. O
pacote de dados enviados pelo MPI requer vários pedaços de informações:
O processo transmissor.
O processo receptor.
O endereço inicial de memória para onde os itens de dados deverão ser mandados.
A mensagem de identificação.
O grupo de processos que podem receber a mensagem.
Ou seja, tudo isso ficará sob responsabilidade do programador em identificar o paralelismo e
implementar um algoritmo utilizando construções com o MPI.
O programa “Olá Mundo” (Hello World), a seguir, apresenta a estrutura básica de um programa
MPI escrito em C:
#include “mpi.h” 
#include <stdio.h> 
int main(int argc,char *argv[]) { 
int meu_id, numero_processos; 
 
// Rotinas de inicialização 
MPI_Init(&argc,&argv); 
MPI_Comm_size(MPI_COMM_WORLD, &numero_processos); 
MPI_Comm_rank(MPI_COMM_WORLD, &meu_id); 
 
fprintf(stdout,”Olá mundo! Sou o processo %i de %i criados”, meu_id, numero_processos); 
// Rotinas de finalização 
MPI_Finalize(); 
return 0; 
} 
A primeira linha desse programa inclui a biblioteca do MPI em C. O comando MPI_Init, com os
argumentos passados pela linha de comando do mpirun, inicializa o ambiente MPI para esse
processo.
O comando MPI_Comm_size(MPI_COMM_WORLD, &numero_processos) retorna na
variável "numero_processos" o número total de processos criados.
O comando MPI_Comm_rank(MPI_COMM_WORLD, &meu_id) retorna na variável "meu_id"
a identificação única desse processo, dentre os n processos criados.
 ATENÇÃO
É importante destacar que os valores retornados variam de 0 até o número total de processos
–1.
O parâmetro MPI_COMM_WORLD, usado por esses comandos, identifica um objeto local, que
representa o contexto de uma comunicação. Processos podem se comunicar, desde que
estejam no mesmo grupo de comunicação. 
O comando MPI_Finalize() encerra o ambiente MPI para esse processo.
Nesse exemplo, cada um desses n processos criados executará o comando fprintf.
Consequentemente, n mensagens serão impressas. A identificação do processo usado nesse
comando serve para diferenciar qual processo escreveu qual mensagem.
Exemplo de código MPI bloqueante
Nesse exemplo, é explorada a comunicação síncrona, na qual o processador que envia a
mensagem aguarda um sinal do destinatário confirmando o recebimento da mesma.
# include <mpi .h> 
# include <stdio .h> 
# define ROOT 0 
# define MSGLEN 100 
int main (int argc , char ** argv ) 
{ 
 int i, rank , size , nlen , tag = 999; 
 char name [ MPI_MAX_PROCESSOR_NAME ]; 
 char recvmsg [ MSGLEN ], sendmsg [ MSGLEN ]; 
 MPI_Status stats ; 
 MPI_Init (& argc , & argv ); 
 MPI_Comm_rank ( MPI_COMM_WORLD , & rank ); 
 MPI_Comm_size ( MPI_COMM_WORLD , & size ); 
 MPI_Get_processor_name (name , & nlen ); 
 sprintf ( sendmsg , " Hello world ! I am process %d out of %d on %s\n", rank , size , name ); 
 if ( rank == ROOT ) { 
 printf (" Message from process %d: %s", ROOT , sendmsg ); 
 for (i = 1; i < size ; ++i) { 
 MPI_Recv (& recvmsg , MSGLEN , MPI_CHAR , i, tag , MPI_COMM_WORLD , & stats ); 
 printf (" Message from process %d: %s", i, recvmsg ); 
 } 
 } else { 
 MPI_Send (& sendmsg , MSGLEN , MPI_CHAR , ROOT ,

Outros materiais