• Nenhum resultado encontrado

cpd parte1

N/A
N/A
Protected

Academic year: 2021

Share "cpd parte1"

Copied!
53
0
0

Texto

(1)

Computação  Paralela  e  

Sistemas  Distribuídos  

   

Prof.:  Felipe  Domingos    

Universidade de Itaúna Faculdade de Engenharia

(2)

Programa  do  Curso  

  Parte  I:  Programação  Mul:thread/Paralela  

–  Arquiteturas,  Conceitos  e  Caracterís:cas,  [1]   –  Implementação  de  Seções  Crí:cas  [1]  

–  Semáforos  e  Monitores  [1]   –  Estudo  de  Caso:  Java  [4]  

  Parte  II:  Programação  Distribuída  

–  Conceitos  e  Caracterís:cas  [1,2,3]   –  Middlewares  [3]  

–  Estudo  de  Caso:  Java  RMI  [3]  e  CORBA  [3]   –  Algoritmos  Distribuídos  [2]  

  Obje:vo:  conciliar  teoria  (fundamentos)  tecnologia  

(ferramentas)  e  prá:ca  (problemas)  

  Bibliografia:  

[1]  Andrews,  G.  Founda'ons  of  Mul'threaded,  Parallel,  and  

Distributed  Programming,  Addison-­‐Wesley,  2000.    

[2]  Couloris,  G.,  Dollimore,  J.,  Kindberg,  T.      Distributed  Systems  

Concepts  and  Design  (Third  Edi:on).  Addison-­‐Wesley,  2001.  

[3]  Emmerich,  W.  Engineering  Distributed  Objects.  John  Wiley  &   Sons,  2000.  

[4]  Arnold,  K.  et  al.  The  Java  Programming  Language,  Third   Edi:on,    Addison-­‐Wesley,  2000.  

(3)

Programação  Concorrente  (cap.  

1)

 

  Programas  seqüenciais:  único  fluxo  execução  

  Programa  concorrentes:vários  fluxos  execução  

–  Mul:thread,  Paralelos  ou  Distribuídos  

  Mul@thread:  

–  núm.  fluxos  execução  >  núm.  processadores   –  Obje:vo:   fazer  várias  coisas,  mesmo  tempo   –  Ex.:GUI,  SO,  SGBD,  servidores  Web  etc  

  Paralelos:  

–  núm.  fluxos  execução  =  núm.  processadores   –  Obje:vo:   fazer  uma  tarefa  mais  rápido  

–  Ex.:  mul:plicar  matrizes,  previsão  tempo,  processar/ gerar  imagem,  simulações  etc  

  Distribuídos:  

–  fluxos  de  execução  em  nodos  de  uma  rede   –  Obje:vo:   compar:lhar  recursos  

–  Ex.:aplicações  C/S  (mail,  web,  dados  etc)  

  Comunicação/Sincronização:  

–  Mem.  compar:lhada:  mul:threads,  paralelos   –  Troca  de  mensagens:  todos  os  3  :pos    

(4)

Programação  Concorrente  

  Origens:  canais  nos  primeiros  SO  (década  60)  

–  Canais:  processadores  de  E/S  

–  Exemplo:  SO  grava  dados  em  um  buffer;  canal   transfere  do  buffer  para  disco  

–  SO  deve  esperar  buffer  vazio;  canal  deve  esperar   buffer  cheio  

  Atualmente,  cada  vez  mais  comum:  

–  GUI,  Internet,  Web,  C/S  arq.  paralelas  etc  

  Programação  concorrente  é  mais  desafiadora  que  

programação  seqüencial  

  Principal  dificuldade:  sincronização  

  Exclusão  Mútua:  assegurar  que  seções  crí:cas  não  

sejam  executadas  ao  mesmo  tempo  

–  Dois  processos  não  podem  gravar  ao  mesmo  tempo   no  buffer  

  Sincronização  condicional:  atrasar  a  execução  até  que  

uma  condição  seja  verdadeira    

(5)

Conceitos  e  Arquiteturas  

  Fluxos  execução  assíncronos:  cada  fluxo  de  execução  

tem  a  sua  própria   velocidade  

  Fluxo  de  execução:  pilha  (variáveis  locais)  +  PC  

  Processo:  fluxo  execução  +  espaço  de  endereçamento  

próprio  

  Thread  ( processos  leves ):  fluxo  execução  +  espaço  de  

endereçamento  compar:lhado  

  Arquiteturas  Monoprocessadas:  

–  Programação  seqüencial  com  interrupções   –  Núcleo  de  mul:programação  (SO,  VM,  ou  

biblioteca)  cria  um  modelo  de  programação   assíncrono  

–  Vários  processos  compar:lham  CPU  

  Arquiteturas  Mul@processadas:  

–  Memória  Compar:lhada   –  Memória  Distribuída  

(6)

  Podem  ser:  

–  UMA  (Uniform  Memory  Access  Time)  

–  NUMA  (Non-­‐Uniform  Memory  Access  Time)  

  Exemplo  (CENAPAD):  Starfire  ENT1000  (Sun),  32  

processadores,  1MB  cache,  8GB  RAM  

  Comunicação:  via  memória  compar:lhada  ou  troca  de  

mensagens  

  Vantagem:  barramento  de  alta  velocidade,  memória  

compar:lhada  facilita  programação  

  Desvantagem:  limitação  no  número  de  nodos  

Mul@processadores  com  

Memória  Compar@lhada  

(7)

Mul@processadores  com  

Memória  Distribuída  

  Processos podem acessar apenas memória local

  Processos se comunicam por troca de msgs

  Exemplo (CENAPAD): IBM RS/6000, 48

processadores

  Vantagem: escalabilidade

  Desvantagem: dificuldade de programação

  Memória Compartilhada Distribuída (DSM):

abstração de um espaço de endereçamento único criada pelo SO

–  SO mapeia endereço virtual para endereço físico de um processador

(8)

Clusters  

  Computadores   comuns  conectados  em  rede  

  Cada  nodo  tem  sua  CPU,  memória  e  SO  

  Vantagens:  escalabilidade,  custo,  tolerância  a  falhas  

  Desvantagens:conexão  em  rede  e  administração  

–  Custo  de  administração  similar  ao  de  n  máquinas   independentes  

  Exemplo:  Google  

–  Vários  clusters,  distribuídos  no  mundo   –  15  mil  PCs  (de  Celeron  a  Dual  Pen:um  III)   –  Sistema  operacional:  Linux  

–  PCs  possuem  discos  IDE  de  80  GB  

–  Obje:vo:  performance  +  hardware  barato  

–  Ver:  Web  Search  for  a  Planet:  The  Google  Cluster  

(9)

Mul@plicação  Matrizes  

(Memória  Compar@lhada)  

  Mul:processadores  com  memória  compar:lhada  

  Idéia:  calcular  produtos  internos  paralelamente  

  Variáveis  compar:lhadas:    

double  a[n,n],  b[n,n],  c[n,n]  

  Código  da  mul:plicação:  

process  row  [i=  0  to  n-­‐1]      %  cria  n  processos        for  j:=  1  to  n-­‐1  do  

           c[i,j]=  0;  

           for  k:=  1  to  n-­‐1  do  

                 c[i,j]  =  c[i,j]  +  a[i,k]  *  b[k,j]  

  Na  solução  acima,  cada  processo  gera  uma  linha  da  

resposta  

  Outra  solução:  computar  cada  produto  interno  em  

paralelo  

 process  [i=  0  to  n-­‐1,  j=  0  to  n-­‐1]    %  cria  n*n  proc    c[i,j]=  0;  

 for  k:=  1  to  n-­‐1  do  

(10)

Mul@plicação  de  Matrizes  

(Memória  Distribuída)  

process  worker[i=0  to  n-­‐1]  {    

   double  a[n];  double  b[n,n];  double  c[n];  

   receive  ini:al  values  for  vector  a  and  matrix  b;        for  [j=0  to  n-­‐1]  {    

       c[j]=0.0;    

       for  [k=0  to  n-­‐1]    

           c[j]=c[j]  +  a[k]*b[k,j];        }    

   send  result  vector  c  row  to  the  coordinator  process;     }    

process  coordinator  {    

   double  a[n,n];  double  b[n,n];  double  c[n,n];        ini:alize  a  and  b;      

   for  [i=0  to  n-­‐1]  {    

       send  row  i  of  a  to  worker[i];            send  all  of  b  to  worker[i];        }    

   for  [i=0  to  n-­‐1]    

       receive  row  i  of  c  from  worker[i];        print  the  result  matrix  c;    

(11)

IPC  via  Unix  Pipes  

  Pipes:  mecanismo  de  IPC  (Inter  Process  Comm.)  

  Fila  de  bytes  unidirecional  residente  no  kernel  

   Criação  de  um  pipe:  

int  d  [2];   int  pipe  (d);  

  Escrita  e  leitura  

int  write  (d[1],   hello  world );      

int  read  (d[0],  buffer,  tam_buffer);  

  Pipes  entre  processos  diferentes:  

–  Criado  por  processo  pai;  em  seguida,  pai  executa   um  fork  

–  Pai  e  filho  passam  a  compar:lhar  o  pipe  

  Somente  pode  ser  usado  entre  processos  pai  e  filho  ou  

entre   processo  irmãos  

  Se  necessário  fluxo  bidirecional:  dois  pipes  

  Comando  Unix:    who  |  sort  |  lpr  

  Outros  mecanismos  IPC  em  Unix:  FIFO,  Fila  de  

(12)

Produtores/Consumidores  via  

Pipes  

void  main  ()  {      //  comunicação  de  pai  para  filho          int  n,  d[2];  

       pid_t  pid;  

       char  line  [MAX_LINE];          if  (pipe  (d)  <  0)  

               prin€  ( Erro  na  criação  do  pipe );          else  ((pid  =  fork())  <  0)  

                 prin€  ( Erro  no  fork );  

       else  if  (pid  >  0)  {    //  processo  pai:  pid=  PID  filho                close  (d[0]);  

             write  (d[1],   hello  world\n );          }  

       else  {  //  processo  filho:  pid  =  0                      close  (d[1]);  

                 n=  read  (d[0],  line,  MAX_LINE);    //  síncrona                    write  (1,  line,  n);                    //  1  =  stdout          }  

(13)

Processos  e  Sincronização  (cap.  2)

 

  Estado:  valor  das  variáveis  de  um  processo  em  um  

instante  (incluindo  PC,  stack  etc)  

  Processo  executa  uma  seqüência  de  comandos  

  Comando:  sequëncia  de  ações  atômicas  

  Ação  atômica:ação  que  inspeciona/altera  estado  de  

forma  indivisível  

–  Exemplos:  instruções  de  máquina  que  acessam   palavras  da  memória  

  Execução  de  programa  concorrente  gera  uma  história:  

s0  →  s1  →  ...  →  sn      (si:  ação  atômica)  

  Histórias  podem  variar  de  execução  para  outra  

  Exemplo  (atribuições  e  leituras  atômicas):  

int  y=  0,  z=  0;  

P1::    x  =  y  +z;                P2::  y=  1;  z=  2;  

  Algumas  histórias  possíveis    

–  rd  y  →  rd  z  →  x=  y+z  →  y=  1  →  z=  2              x=  0   –  y=  1→  rd  y  →  rd  z  →  x=  y+z  →  z=  2                x=  1   –  rd  y  →  y=  1  →  z=  2  →  rd  z  →  x=  y+z              x=  2   –  y=  1  →  z=  2  →  rd  y  →  rd  z  →  x=  y+z              x=  3  

(14)

Sincronização  

  Sincronização:  restringe  as  histórias  possíveis  

  Exclusão  mútua:  requer  a  criação  de  ações  atômicas  via  

so†ware    

–  Uma  ação  atômica   de  maior  granularidade  é   chamada  de  seção  crí:ca  

  Exemplo:  <  ...  >  cria  uma  seção  crí:ca  

int  y=  0,  z=  0;  

P1::    <x  =  y  +z;>              P2::  <y=  1;  z=  2;>  

  Histórias  possíveis:  

–  rd  y  ;  rd  z  ;  x=  y+z    →    y=  1  ;  z=  2                          x=  0   –  y=  1  ;  z=  2  →    rd  y  ;  rd  z  ;  x=  y+z                            x=  3  

  Sincronização  condicional:  atrasar  uma  ação  atômica  

até  que  uma  condição  seja  verdadeira  

–  Até  que  o  estado  atenda  uma  condição  

  Propriedade:  condição  que  é  verdadeira  em  qualquer    

história  de  um  programa  concorrente  

(15)

Safety  e  Liveness  

  Safety:  propriedades  que  asseguram  a  não    ocorrência  

de  um  estado  indesejável  

  Liveness:  propriedade  que  assegura  a  ocorrência  de  um  

estado  desejável  

  Dado  um  programa  concorrente  mostrar:  

–  Não  ocorre  deadlock/livelock                              (safety)   –  Possui  uma  seção  crí:ca  (SC)                        (safety)   –  Termina                                                                                        (liveness)   –  Eventualmente  entra  em  uma  SC        (liveness)  

  Como  provar  que  uma  propriedade  é  atendida?  

–  Testes  ( provam  a  presença  de  erros,  mas  não  a   ausência )  

–  Formalmente  (via  asserções  etc)  

  Notação  (similar  a  linguagem  SR):  

co   bloco  de  comandos  

co   bloco  d  comandos //  em  SR,   //    

oc  

  Braços  de  um  comando  co  são  executados  em  

paralelo  (comando  termina  quando  todos  braços   terminarem)  

(16)

Independência  de  Processos  

Concorrentes  

  read  set:  variáveis  lidas  e  não  alteradas     write  set:  variáveis  que  são  alteradas  

  Independência:  dois  blocos  de  comandos  são  

independentes  se  o   write  set  de  cada  bloco  é  disjunto   do   read  set  e   write  set  do  outro  bloco  

  Exemplo:    

–  P1::    x  =  y  +z;                              P2::  y=  1;  z=  2;  

     read_set(P1)=  {y,  z}            write_set(P2)=  {y,  z}  

  race  condi@on:  quando  programa  permite  a  execução  

concorrente  de  blocos  de  comandos  não  independentes   (ou  conflitantes)  

  Exemplo:  como  paralelizar  um  grep?  

 String  line;  

read  a  line  of  input  from  stdin  into  line;   while  (!EOF)  {  

               look  for  pa‰ern  in  line;          if  (pa‰ern  is  in  line)                          write  line;  

       read  next  line  of  input;   }  

(17)

Exemplo:  grep  paralelo  

  Versão  incorreta  (com  conflitos):  

String  line1;  

read  a  line  of  input  from  stdin  into  line1;   while  (!EOF)  {    

       co  look  for  pa‰ern  in  line1;                //  read  set=  line1  

         if  (pa‰ern  is  in  line1)  print  line1  

       co  read  next  line  into  line1;              //  write  set=  line1  

       oc   }  

  Versão  correta:  

String  line1,line2;  

read  a  line  of  input  from  stdin  into  line1;   while  (!EOF)  {    

       co  look  for  pa‰ern  in  line1;                    //  read  set=  line1  

         if  (pa‰ern  is  in  line1)  print  line1  

       co  read  next  line  into  line2;                  //  write  set=  line2  

       oc  

line1  =  line2;     }    

  Desvantagens:  cópia  entre  linhas  e  criação  de  duas  

(18)

Sincronização  

  Execução  de  programa  concorrente:  intercalação  de  

ações  atômicas    

  Sincronização:  evita   intercalações  indesejadas  

–  Via  seções  crí:cas    

–  Via  sincronização  condicional  

  Ação  atômica  sem  condição  (ou  incondicional):  

–  <  S  >  :  executa  comandos   S  atomicamente  

  Ação  atômica  com  condição  (ou  condicional):  

–  <  await  (B);  S  >:  espera  ocorrência  da  condição  B,   então  executa   S  atomicamente  

  Exemplo:    <  await(s>0);  s=s-­‐1  >                <  s=  s+1  >  

–  Que  operações  são  estas  acima?  

  Exemplo:  <  x=  x+1;  y=  y+1;  >  

–  Estados  internos  (como  x  já  incrementado  e  y   não)  não  são  visíveis  a  outros  processos.  

  Regiões  crí:cas  são  usadas  para  tratar  blocos  de  código  

conflitantes  

–  No  entanto,  definir  a  granularidade  de  uma  seção   crí:ca  não  é  simples  

(19)

Exemplo:  Maior  elemento  de  

um  vetor  de  inteiros  

  Versão  seqüencial:  

 int  m  =  0;  

         for  [i=0  to  n-­‐1]            if  (a[i]>m)                m=a[i];    

  Versões  incorretas:  

co  [i=0  to  n-­‐1]      

     if  (a[i]>m)    

           m=a[i];    

//  write_set(i)=  m

   

co  [i=0  to  n-­‐1]      

     if  (a[i]>m)    

           <  m=a[i];    >  

//  serialização  arbitrária  

  Melhor  versão:  

         int  m  =  0;  

         co  for  [i=0  to  n-­‐1]            if  (a[i]>m)  

                     <  if  a[i]  >  m                            m=a[i];  >  

         (só  sincroniza,  se  for   atualizar   m )  

  Versão  com  baixo  

paralelismo:            int  m  =  0;  

         co  for  [i=0  to  n-­‐1]              <  if  a[i]  >  m                            m=a[i];  >  

     (muito  parecido  com        versão  seqüencial)  

(20)

Sincronizando  Produtores  e  

Consumidores  

  Copiar  um  vetor  de  inteiros   a  de  um  processo  para  

outro,  usando  um  buffer  compar:lhado  

  Buffer  é  uma  único  inteiro   buf  (compar:lhado)  

int    buf,  p=0,  c=0;                                    //  p:  inteiros  produzidos  

process  Producer  {                            //  c:  inteiros  consumidos  

   int  a[n];    

   while  (p<n)  {    

       <  await  (p==c);  >              //  produzido  já  foi  consumido          buf=a[p];              //   produz          p=p+1;     }     process  Consumer  {        int  b[n];        while  (c<n)  {    

       <  await  (p>c);  >        //  produzido  não  foi  consumido          b  [c]=buf;                            //   consome  

       c=c+1;        }    

}    

(21)

Polí@cas  de  Escalonamento  

  Vários  processos,  com  várias  ações  candidatas  a  

execução.  Como  escolher/eleger  uma  ?  

–  Por  meio  de  uma  polí:ca  de  escalonamento  

  Escalonamento  incondicionalmente  justo  (fair):  uma  

ação  atômica  incondicional  candidata  a  execução  é   eventualmente  executada  

  Escalonamento  fortemente  justo:  

–  incondicionalmente  justo  

–  Em  <await  B;  S>,  se  B  às  vezes  é  falsa  e  às  vezes  é   verdade,  então  a  ação  não  é  sempre  selecionada   quando  sua  condição  é  falsa.  

  Escalonamento  fracamente  justo:  quando  não  garante  a  

segunda  condição  acima.  

  Exemplo:  

bool  con:nue=  true,  try=  false;  

co  while  (con:nue)  {  try  =  true;  try=  false;  }   co  <await  (try)  con:nue=  false>;  

  Se  fortemente  justo,  programa  sempre  termina  

  Se  fracamente  justo,  pode  nunca  terminar  

(22)

Locks  e  Barreiras  (cap.  3)  

  Como  implementar  uma  seção  crí:ca(SC)  ?  

–  Resposta:  por  meio  de  <  ....  >    

  Mas  como  implementar  <  ....  >  ?  

–  Resposta:  por  meio  de  locks  

  Problema  da  SC:  

process  CS[i  =  1  to  n]  {  

   while  (true)  {          entry  protocol;          cri:cal  sec:on;            exit  protocol;          noncri:cal  sec:on;      }   }           Propriedades:  

–  Exclusão  Mútua  (safety)  

–  Ausência  de  deadlocks/livelocks  (safety)   –  Ausência  de  atrasos  desnecessários  (safety)   –  Entrada  em  tempo  finito  (liveness)  

(23)

Seção  Crí@ca  via  Locks  

  lock:  variável  booleana  que  indica  se  algum  processo  

encontra-­‐se  na  SC:  

–  lock=  true:  algum  processo  está  na  SC   –  lock=  false:  nenhum  processo  está  na  SC  

  SC  com  locks:  

<  await  (!lock)  lock  =  true;  >    

cri:cal  sec:on     lock  =  false;    

  Como  implementar  o  await  acima?  

–  Via  uma  instrução  de  hardware  

  Test  and  Set:  

bool  TS(bool  lock)  {                            <  bool  ini:al  =  lock;  

       lock  =  true;          return  ini:al;  >   }  

  SC  por  meio  de  Test  and  Set:  

while  (TS(lock))  skip;                                                      //  CSEnter  

cri:cal  sec:on;    //  TS(lock)  retornou  false;  lock=true  

(24)

Seção  Crí@ca  via  Locks  

  Exclusão  Mútua:  

–  Dois  processos  tentam  entrar  mesmo  tempo  

–  Um  deles  executa  TS(lock)  primeiro  e  seta  lock  para   true  (entrando  na  SC)  

–  Outro,  executa  TS(lock),  retorna  true  e  espera  

  Ausência  de  deadlocks:  

–  Se  2  processos  bloqueados,  lock=true  sempre   –  Mas  para  lock  ser  true,  significa  que  um  processo  

entrou  na  SC;  ele  tem  que  sair  e  fazer  lock=  false  

  Ausência  de  atrasos  desnecesários:  

–  Se  processo  fora  da  SC,  então  ele  não  pode  bloquear  a   entrada  do  outro  

  Entrada  em  tempo  finito:  

–  Requer  escalonamento  fortemente  justo  

–  Nesse  caso,  assegura-­‐se  que   processo  atrasado   irá,  em  algum  momento,  chamar  TS(lock)  quando   lock=false  

  Por  fim,  <await  (B);  S>  é  implementado  como:  

CSEnter;      

(25)

Algoritmo  Tie-­‐Breaker    

  Solução  fair  para  SC,  com  dois  processos:  

–    in1  e   in2 :  processo  está  ou  não  na  SC   –  last :  úl:mo  proc.  iniciou  protocolo  entrada  

  Algoritmo:  

bool  in1=  false,  in2=  false;  int  last=  1;  

process  P1  {  

       while  (true)  {  

               in1=  true;  last=  1;  

               while  (in2  and  last  ==1)  skip;                  cri:cal  sec:on;                  in1=  false;                  non-­‐cri:cal  sec:on;          }   }   process  P2  {          while  (true)  {  

               in2=  true;  last=  2;  

               while  (in1  and  last  ==2)  skip;                  cri:cal  sec:on;  

               in2=  false;  

(26)

Algoritmo  Tie-­‐Breaker  

  Exclusão  Mútua:  

–  Se  P1  na  SC,  (in2==false  ou  last==2)   –  Logo,  P2  não  pode  estar  também  na  SC  

  Interferência  entre  processos:  

–  Suponha  que  in2=  false  e  P1  tente  entrar     –  Antes  de  P1  entrar,  P2  faz  in2=  true.    

–  P1  entra,  mas  P2  não  consegue  pois  last==2  

  Fair:  

–  Se  P1  e  P2  disputando  SC  (in1=  in2=  true)   –  P2  entrou  (isto  é,  P1  setou  last  por  úl:mo)   –  P2  saiu  e  disputa  SC  (in2=  true;  last=  2)   –  P1  entra  (já  que  last==2)  

  Algoritmo  Tie-­‐breaker:  solução  para  n  processos  é  mais  

(27)

Algoritmo  do  Ticket  

  Solução  fair  para  SC,  com  n  processos  

  Algoritmo  da   senha  do  banco:  cliente  chega  ao  

banco,  pega  uma  senha  e  aguarda  ser  chamado  

  Variáveis:  

–  number :  núm.  da  próx.   senha  a  distribuir   –  next :  núm.  do  próx.  processo  a  ser  atendido   –  turn[n] :  armazena  senha  dos  n  processos  

  Algoritmo:  

int  number=  1,  next=1;    int  turn[n];  

process  CS[i=  1  to  n]  {  

     while  (true)  {  

             <  turn[i]=  number;  number++;  >                <  await  (turn[i]  ==  next);  >  

               cri:cal  sec:on;                  <  next++;  >  

               non-­‐cri:cal  sec:on;        }  

}      

  Depende  de  uma  instrução  FA  (fetch  and  add):  

(28)

Barreiras  

  Ponto  de  sincronização:  todos  os  processos  devem  

a:ngir  este  ponto  para  que  a  execução  de  quaisquer   deles  possa  prosseguir  

  Barreiras  usando  um  contador  compar:lhado:  

–  Quando  processo  a:nge  a  barreira,  contador  é   incrementado  

–  Quando  contador  igual  a   n ,  abre-­‐se  barreira  

  Exemplo:  

int  count=0   ...  

code  to  implement  task  i;    

<  count++;  >          //  barreira   <  await  (count==n);  >  

  Problema:  como  reu:lizar  a  barreira,  isto  é,  como  

resetar  o  contador  ?    

–  Contador  deve  ser  resetado  antes  que  um  processo   tente  incrementá-­‐lo  de  novo  

  Outro  problema:  contenção  acesso  à  memória  

–  (n-­‐1)  processos  inspecionando    contador,  esperando   úl:mo  processo  a:ngir  a  barreira  

(29)

Barreiras  com  Processo  

Coordenador  

  Evita  contenção  no  acesso  à  memória  

–  arrive[i] :  indica  se  processo  i  a:ngiu  barreira   –  con:nue[i] :indica  pode  prosseguir  execução  

  Algoritmo:  

int  arrive[n],  con:nue[n];          //  inicializados  com  zero  

process  Worker[i=1  to  n]  {      

   while  (true)  {    

       code  to  implement  task  i;    

       arrive[i]=1;                                                                        //  anuncia  chegada  

       <  await  (con:nue[i]==1);  >          //  permissão  prosseguir  

       con:nue[i]=0;              //  reseta  flag      }    

}    

process  Coordinator    

   while  (true)  {            for  [i=1  to  n]  {    

           <  await  (arrive[i]==1);  >      //  aguarda  todos  chegarem              arrive[i]=0;    

       }    

       for  [i=1  to  n]  con:nue[i]=1;    //  avisa  podem  prosseguir        }    

(30)

Semáforos  (cap.  4)  

  Implementação  de  SC  não  é  trivial  

  Logo,  necessita-­‐se  de  mecanismos  de  sincronização  de  

mais  alto  nível,  tais  como  

–  <  S  >    e  <  await  (B);  S  >            (linguagem  SR)   –  Semáforos  (cap.  4)  e  monitores  (cap.  5)  

  Semáforos  (Dijkstra,  1968):  variável  inteira  

compar:lhada  entre  processos.    

  Operações  sobre  semáforos:  

–  sem  s;                                    (inicialização)  

–  P(s):  <  await  (s>0)  s  =  s-­‐1;  >                          (ou  wait)     –  V(s):  <  s  =  s+1;  >                                                        (ou  signal)  

  Exemplo  1:  Implementação  de  Seção  Crí:ca  

sem  livre=1;                                            //  livre=  1:  SC  livre  

process  CS[i=1  to  n]  {        while  (true){    

       wait(livre);                      

       cri:cal  sec:on;                                  //  livre=  0:  processo  na  SC          signal(livre);                    

       noncri:cal  sec:on;        }    

(31)

Semáforos  

  Exemplo  2:  Erros  na  u:lização  de  semáforos:  

–  wait(livre);  CS;  wait(livre);                                    (deadlock)                                                

–  signal(livre);  CS;  wait(livre);                  (sem  exclusão)  

  Semáforo  binário:  valor  é  sempre  0  ou  1  

  Semáforo  sinalizador:  inicializado  com  zero  

–  Esperando  um  evento:  wait(s)   –  Sinalizando  evento:  signal(s)  

  Exemplo  3:  Barreiras  (semáforo  sinalizador)  

sem  arrive1=  0,  arrive2=  0;   process  Worker1  {  

      executa  task1 ;        signal(arrive1);        wait(arrive2);  

     ...                //  assegura-­‐se  que  task1  e  task2  executadas  

}  

process  Worker2  {         executa  task2 ;        signal(arrive2);        wait(arrive1);  

     ...            //  assegura-­‐se  que  task1  e  task2  executadas  

(32)

Produtor/Consumidor  

  M  prod.,  N  consumidores  e  buffer  com  um  inteiro  

  Semáforos:  

–  empty:  usado  para  sinalizar  que  buffer  vazio   –  full:  usado  para  sinalizar  que  buffer  cheio  

int  buf;          

sem  empty=1,  full=0;                                                  //  inicialmente  buffer  vazio     process  Producer[i=1  to  M]  {  

   while(true)  {           produce  data  

       P(empty);                                                //  espera  buffer  vazio  para  produzir                                                    buf=data;    

       V(full);                      //  após  produzir,  sinaliza  que  buffer  está  cheio      }  

}  

process  Consumer[j=1  to  N]  {                                    while(true)  {  

       P(full);                                                      //  espera  buffer  cheio  para  consumir            result=buf;  

       V(empty);                                      //  após  consumir,  sinaliza  buffer  vazio      }  

(33)

Produtor/Consumidor  com  

Buffer  de  Tamanho  n  

  Variáveis:  

–  rear:  índice  onde  será  inserido  próximo  valor   –  front:  índice  de  onde  será  lido  próximo  valor  

  Valores  armazenados  no  buffer:  iniciam  em  front  e  

terminam  em  rear-­‐1,  de  forma  circular  

  Possível  intercalar   produtores  com  

consumidores  (em  posições  diferentes)  

  Intercalações  impossíveis:   produtores  com  

produtores ,   consumidores  com  consumidores  

  Semáforos  para  criar  SC:  mutexP  e  mutexC  

–  Produtor:      P(mutexP);   produz ;  V(mutexP)   –  Consum.:    P(mutexC);   consome ;  V(mutexC)  

  Semáforos  para  sinalizar  mudanças  no  buffer:  

–  empty:  armazena  número  de  posições  vazias   –  full:  armazena  número  de  posições  ocupadas   –  empty+full  ==  n  (por  construção  do  algoritmo)  

  Antes  produzir,  deve  exis:r  slot  vazio:  P(empty)  

  Após  produzir,  um  slot  ficou  ocupado:  V(full)  

(34)

Produtor/Consumidor  com  

Buffer  de  Tamanho  n  

int  buf[n];          

int  front=0,  rear=0;   sem  empty=n,  full=0;    

sem  mutexP=1,  mutexC=1                                              #  for  mutual  exclusion   process  Producer[i=1  to  M]  {                                                                #  M  producers      while(true)  {  

        produce  data  

       P(empty);                                                                #  block  if  buffer  full  -­‐  empty  is  0          P(mutexP);                        #  block  mul'ple  producers  to  access  buffer          buf[rear]=data;  rear=(rear+1)%n;  

       V(mutexP);                                                                                        #  leave  cri'cal  sec'on          V(full);                                          #  increment  full  counter  -­‐  split  semaphore      }  

}  

process  Consumer[j=1  to  N]  {                                                          #  N  consumers      while(true)  {  

       P(full);                    #  block  if  buffer  empty  -­‐  full  is  0          P(mutexC);                #  block  mul'ple  consumers  to  access  buffer          result=buf[front];  front=(front+1)%n;  

       V(mutexC);                                                                                    #  leave  cri'cal  sec'on          V(empty);                  #  increment  empty  counter  -­‐  split  semaphore  

          consume  result  

(35)

Jantar  dos  Filósofos  

  Filósofos pensam, comem, pensam, comem, ...

  Para comer, precisam de dois garfos

  Existem apenas cinco garfos na mesa

  Solução: vetor de semáforos representa garfos

–  sem fork[5]= {1,1,1,1,1} # garfos estão livres

  Pegar garfo i: P(fork[i])

–  < await fork[i] > 0; fork[i]= fork[i] -1; >

  Liberar garfo i: V(fork[i]);

–  < fork[i]= fork[i]+1; >

  Filósofos pegam o garfo da esquerda e, logo em

seguida, o garfo da direita

P(fork[i]);

P(fork[i+1] mod 5); eat

(36)

Jantar  dos  Filósofos  

  Deadlock:  filósofos  morrem  de  fome  quando  cada  um  

pega  o  garfo  à  sua  esquerda  e  se  recusa  a  liberá-­‐lo  

  Deadlock:  é  um  ciclo  de  espera    

–  P1  espera  por  recurso  com  P2;     –  P2  espera  por  recurso  com  P3;     –  ...  

–  Pn  espera  por  recurso  com  P1  

  Solução:  quebrar  o  ciclo  acima  

  Filósofo  4  pega  primeiro  o  garfo  à  sua  direita  

 (isto  é,  garfo  à  esquerda  do  Filósofo  3)  

  Assim,  Filósofo  3  e  4  disputam  este  garfo:  quem  ganhar,  

come  (e  evita-­‐se  o  deadlock)  

  Outra  solução:  limitar  a  quatro  o  número  de  filósofos  

que  podem  comer  simultaneamente   –  sem  room=  4;  

–  Ao  ficar  com  fome:    P(room)   –  Após  comer:    V(room)  

(37)

Leitores  e  Escritores  

  Dois  :pos  de  processos  -­‐-­‐  leitores  e  escritores  -­‐-­‐  

compar:lham  um  banco  de  dados:  

–  Escritores  devem  ter  acesso  exclusivo  ao  BD   –  Vários  leitores  podem  acessar  juntos  o  BD  

  Escritor:  acessa  BD  em  uma  SC  (semáforo  rw=1)  

–  P(rw);   write  database ;    V(rw)  

  Leitor:  

–  nr:  número  de  leitores  a:vos  (acessando  BD)   –  Ao  inciar  leitura:  incrementar  nr    

–  Ao  terminar  leitura:  decrementar  nr   –  Se  primeiro  leitor  a  usar  (nr  ==  1):  P(rw)   –  Se  úl:mo  leitor  a  usar  (nr  ==  0):  V(rw)  

  Primeira  versão  do  leitor:  

process  Reader[i=  1  to  M]  {      while(true)  {          <  nr++;  if  (nr  ==  1)  then  P(rw);  >          "read  database"          <  nr-­‐-­‐;  if  (nr  ==  0)  then  V(rw);  >      }   }  

(38)

Leitores  e  Escritores  

int  nr=  0;                                                                                          #  number  of  ac've  readers   int  rw=  1;                                                                      #  lock  for  reader/writer  exclusion   sem  mutexR=  1;                                                          #  lock  for  reader  access  to  nr   process  Reader[i=  1  to  M]  {  

   while(true)  {          P(mutexR);      

           nr++;  if  (nr  ==  1)  then  P(rw);                                                  #  if  first,  get  lock          V(mutexR)  

       "read  database"          P(mutexR)  

           nr-­‐-­‐;  if  (nr  ==  0)  then  V(rw);                                        #  if  last,  release  lock          V(mutexR)  

   }   }  

process  Writer[j=  1  to  n]  {      while  (true)  {  

       ....  

       P(rw);  "write  the  database ;      V(rw);      }  

}  

  Esta  solução  não  é  fair,  pois  privilegia  leitores  

  Se  um  leitor  usando;  e  se  um  leitor  e  um  escritor  

(39)

Monitores  (cap.  5)  

  Mecanismo  de  sincronização  de  mais  alto  nível,  quando  

comparado  com  semáforos    

–  C.A.R  Hoare  (1974)  e  B.  Hansen  (1975)  

  Adotado  em  várias  linguagens,  como  em  Java  

  Monitor=  variáveis  +  procedimentos  (=  objeto)  

–  Monitor:  mecanismo  de  abstração  de  dados  

  Suporta  automa:camente  exclusão  mútua:  em  um  

dado  instante,  apenas  um  processo  pode  executar   procedimentos  de  um  monitor  

  Logo,  compilador  (e  não  o  programador)  fica  

responsável  por  assegurar  exclusão  mútua  

  Sintaxe:  

monitor  <name>  {  

       <declaração  de  variáveis>          <procedimentos>  

}  

  Chamando  procedimento  de  um  monitor:  

call  <name>.<opname>  (  <argumentos>  )  

(40)

Monitores    

  Sincronização  condicional  (await)  em  monitores:  

implementada  usando   variáveis  condicionais   –  Sintaxe:  cond  cv;                  //  cv  é  uma  var.  condicional  

  Valor  de  uma  var.  condicional:  fila  de  processos  

  Manipulando  variáveis  condicionais:  

–  empty(cv):  fila  de  cv  vazia  ?  

–  wait(cv):  processo  se   auto-­‐enfilera  na  fila  de  cv  e   libera  o  monitor  

–  signal(cv):   acorda  o  processo  na  1a  posição  da  fila   associada  a  cv  

  Existem  duas  semân:cas  para  um  signal:  

–  Signal  and  Con:nue  (SC):  quem  emi:u  o  signal   con:nua  executando  (processo  sinalizado  executa   mais  tarde)  

–  Signal  and  wait  (SW):  quem  emi:u  o  signal   libera   o  monitor  (processo  sinalizado  então  inicia  

execução)  

  Exemplos  a  seguir  assumem  semân:ca  SC.  Usada  no  

(41)

Exemplo  1:  Semáforos  

  Implementando  semáforos  usando  monitores:  

monitor  semáforo  {  

   int  s=  0;                                                                          //  valor  do  semáforo      cond  pos;                                    //  fila  de  processos  esperando      procedure  P()  {            while  (s  ==  0)  wait(pos);            s=  s  -­‐  1;      }      procedure  V()  {            s=  s  +  1;            signal(pos);      }            }    

(42)

Exemplo  2:  Produtor/

Consumidor  

monitor  Buffer  {    

   int  buf[n];                  #  buffer  

   int  primeiro=0,      #  primeira  posição  ocupada  em  buf        ul:mo=0;              #  primeira  posição  livre  em  buf      cont=0;        #  número  de  slots  ocupados  em  bh  

   cond    full,        #  produtores  esperando  porque  buf  cheio      empty;        #  consumidores  esperando  porque  buf  vazio    

   procedure  inserir  (int  dado)  {    

       while  (cont==n)  wait(full);                #  enquanto  buf  cheio,  esperar          buf[ul:mo]=dado;    

       ul:mo=(ul:mo+1)  %  n;            cont++;    

       signal  (empty);                                              #  acorda  consumidor  esperando      }    

 

   procedure  re:rar  (int  &dado)  {    

       while  (cont==0)  wait  (empty);          #  enquanto  buf  vazio,  espera          dado=buf[primeiro];                                        

       primeiro=(primeiro+1)  %  n;            cont-­‐-­‐;    

       signal(full);                                                                #  acorda  produtor  esperando      }    

(43)

Estudo  de  Caso:  Threads  em  

Java  

  Solução  1:  subclasse  de  Thread  

class  PingPong  extends  Thread  {      private  String  palavra;  

   public  PingPong(String  palavra)  {    this.palavra=  palavra;    }  

   public  void  run()  {        //  threads  devem  implementar  o  método  run            try  {  

             for(int  i=  0;  i  <  1000;  i++)  {    System.out.print(palavra  +  "  ");  Thread.sleep (100);    }  

         }  

         catch  (InterruptedExcep:on  e)  {  return;    }        }    

   public  sta:c  void  main(String  []  args)  {          Thread  t1=  new  PingPong("ping");              Thread  t2=  new  PingPong("PONG");            t1.start();    t2.start();  

   }           }    

  Solução  2:  implementar  interface  Runnable  

class  PingPong  implements  Runnable  {  

       ...      //  mesmo  código  do  exemplo  anterior        public  sta:c  void  main(String  []  args)  {  

               Runnable  ping=  new  PingPong("ping");                      Runnable  pong=  new  PingPong("PONG");                  new  Thread(ping).start();    

(44)

Sincronização  

  Seja  o  exemplo  abaixo:  

class  ContaBancaria  {      private  double  saldo;  

   public  double  getSaldo()  {  return  saldo;  }  

   private  setSaldo(double  saldo)  {  this.saldo=    saldo;  }      public  doube  depositar(double  valor)    

   {  double  s=  getSaldo();  s=  s+valor;  setSaldo(s);    }       }    

  Se  duas  threads  chamarem  depositar(100):  

                                                           

 

Thread 1 Thread 2 Saldo

s= getSaldo(); (s= 100) 100 s= getSaldo() ; (s= 100) s= s+ valor; (s= 200) s= s+ valor; (s= 200) setSaldo(s); 200 setSaldo(s); 200

  Como criar seções críticas? Por meio da palavra

synchronized

class ContaBancaria {

private double saldo; ...

public synchronized doube depositar(double valor) { saldo= saldo + valor; }

public synchronized doube retirar(double valor) { saldo= saldo - valor; }

(45)

Sincronização  

  Todo  objeto  em  Java  possui  um  lock:  informa  se  o  

objeto  foi  ou  não   travado  por  alguma  thread  

  Seja  a  chamada    p.m():                    (m  é  synchronized)    

–  Antes  executar  m,  thread  deve  adquirir  lock  de  p   (i.e.,  travar  p).  Se  p  já  es:ver  travado,  thread  espera   até  ser  destravado.  

–  Quando  thread  terminar  execução  de  m,  ela  libera  o   lock  de  p  

–  Durante  execução  de  m,  thread  pode  chamar  outros   métodos  synchronized  desse  objeto  (sem  esperar)  

  Como  colocar  uma  thread  para  dormir:  por  meio  de  

wait()  

–  Chamado  por  thread  de  posse  do  lock  de  um   objeto  p  

–  Libera-­‐se  o  lock  deste  objeto  

–  Execução  desta  thread  é  suspensa  até  uma   execução  de  no:fy()  ou  no:fyAll()  sobre  p  

–  Ao  ser   acordada ,  thread  volta  a  disputar  o  lock   de  p.  Execução  reinicia  na  linha  seguinte  ao  wait()  

(46)

Sincronização  

  Como   acordar  uma  thread?  no@fy()  ou  no@fyAll()  

  no@fy():  chamado  por  thread  de  posse  do  lock  de  um  

objeto  p  

–  Acorda  (arbitrariamente)  uma  das  threads  que  estejam   dormindo  sobre  este  objeto  

–  Thread  acordada  volta  a  disputar  lock  de  p  

  no@fyAll():  acorda  todas  as  threads  que  estejam  dormindo  

sobre  um  determinado  objeto.  

  Todo  objeto  em  Java  possui  dois  conjuntos:  

–  Conjunto  de  entrada:  threads  que  estão  bloqueadas,   disputando  o  lock  desse  objeto  (i.e.,  chamaram  um   método  sincronizado,  mas  não  iniciaram  a  execução  do   mesmo)  

–  Conjunto  de  espera:  threads  cuja  execução  está   suspensa  

  Operações:  

–  wait():  move  a  thread  corrente  para  o  conjunto  de  

espera  do  objeto  sobre  o  qual  a  operação  foi  chamada.   –  no:fy():  move  uma  thread  arbitrária  do  conjunto  de  

espera  para  o  conjunto  de  entrada  

–  no:fyAll:  move  todas  as  threads  do  conjunto  de  espera   para  o  conjunto  de  entrada  

  Quando  uma  thread  libera  o  lock  de  um  objeto,  uma  

(47)

Produtor/Consumidor  em  Java  

public  class  Produtor  extends  Thread  {  

     private  Buffer  buf;    private  int  numero;        public  Produtor(Buffer  buf,  int  numero)  {            this.buf=  buf;  this.numero  =  numero;        }  

     public  void  run()  {  

           for  (int  i  =  0;  i  <  10;  i++)  {      //  armazena  10  valores  no  buffer                    buf.put(i);  

                 System.out.println("Produtor  #"  +  numero  +  "  put:     +  i);                    try  {  sleep((int)(Math.random()  *  100));  }    

                 catch  (InterruptedExcep:on  e)  {  }              }  

     }   }  

public  class  Consumidor  extends  Thread  {      private  Buffer  buf;    private  int  numero;      public  Consumidor(Buffer  buf,  int  numero)  {          this.buf=  buf;  this.numero  =  numero;  

   }  

   public  void  run()  {  //  re:ra  10  valores  do  buffer          int  x;  

       for  (int  i  =  0;  i  <  10;  i++)  {              x  =  buf.get();  

           System.out.println("Consumidor  #"+  numero  +  "  get:  "  +  x);          }  

(48)

Produtor/Consumidor  em  Java  

  Suponha  um  buffer  com  capacidade  para  apenas  um  inteiro  e  

métodos  get()  e  put():    

public  class  Buffer  {  

   private  int  valor;  private  boolean  cheio  =  false;      public  synchronized  int  get()  {  

       while  (!cheio)  {  

             try  {  wait();  }  catch  (InterruptedExcep:on  e)  {  }          }  

       cheio=  false;  no:fyAll();  return  valor;      }  

   public  synchronized  void  put(int  valor)  {          while  (cheio)  {  

             try  {  wait();  }  catch  (InterruptedExcep:on  e)  {  }            }  

         this.valor=  valor;  cheio=  true;  no:fyAll();        }  

}  

  No  método  get(),  ao  sair  do  wait,  thread  deve  voltar  a  testar  se  buffer  está  

vazio.  Pode  ser  que  outro  consumidor  tenha  ob:do  o  lock  do  objeto  na   frente  e  esvaziado  o  buffer.  Se  isso  :ver  acontecido,  thread  volta  a  dormir  

  No  método  put(),  ao  sair  do  wait,  thread  deve  voltar  a  testar  se  buffer  está  

cheio.  Pode  ser  que  outro  produtor  tenha  ob:do  o  lock  do  objeto  na   frente  e  ocupado  o  buffer.  Se  isso  :ver  acontecido,  thread  volta  a  dormir  

(49)

Leitores  e  Escritores  

  Assuma  que  threads  leitoras  e  escritoras  compar:lham  um  

objeto  de  uma  classe  BD,  a  qual  possui  um  atributo  inteiro   x  e  dois  métodos:  read  (retorna  o  valor  de  x)  e  write  

(incrementa  o  valor  de  x).  A  solução  proposta  deve   permi:r  leituras  concorrentes.  

class  BD  {  

     private  int  x;  private  int  nr=  0;  

     private  synchronized  void  startRead()  {    nr++;  }  

     public  synchronized  void  endRead()  {  nr-­‐-­‐;    if  (nr==0)  no:fy();  }        public  int  read()  {  

           int  t;  startRead();    t=  x;  endRead();  return  t;        }      

   public  synchronized  void  write()  {          while  (nr  >  0)  wait();    

       x++;    no:fy();      }  

}  

  Se  um  escritor  a:vo:  leitor  é  bloqueado  no  startRead()  

  Se  existe  leitor  a:vo:    novos  leitores  conseguem  ler;  novo  escritor  fica  

bloqueado  no  while  

  no:fy  de  endRead:  desbloquear  escritores     no:fy  de  write;  desbloquear  outros  escritores  

(50)

Comando  synchronized  

  Permite  adquirir  o  lock  de  qualquer  objeto  (por  uma  

duração  inferior  à  execução  completa  de  um  método)  

  Exemplo:  

void  abs  (int  []  v)  {  

       synchronized  (v)  {              //  garante  que  v  é  acessado  em  uma   seção  crí:ca  

                 for  (int  i=  0;  i  <  v.lenghth;  i++)                          if  (v[i]  <  0)  v[i]=  -­‐v[i];  

       }   }  

Referências

Documentos relacionados

A pesquisa possibilitou traçar o perfil das intoxicações medicamentosas no Brasil, durante 2013 a 2016, na qual se observou que a região Sudeste do país foi onde ocorreu

Sendo assim, este artigo tem como objetivo apresentar, comparar e combinar abordagens para a previsão da demanda de voos comerciais domésticos, considerando dados

A raiz de possíveis divergências pode resi- dir na maior ou menor ênfase que se atribua a cada uma das três dimensões propostas por Toledo (1994), as quais compõem, em

&#34;Mais de 50% das instalacoes esportivas da cidade estao prontas e temos tempo&#34;, afirmou Ricar- do Leyser, secretario-executivo do go- verno federal para a Rio 2016, durante

Após os procedimentos acima, a Assessoria Financeira envia a Tesouraria a Nota de resgate e as informações sobre a empresa e nº da transação através de e-mail.. Ao receber as

Dessa forma, a força terminal de ativação atrial pode ser usada como ferramenta não invasiva para estratificação de risco de acidente vascular cerebral isquêmico, principalmente

Fita 3, Lado a – Diz que regularização do uso da terra por contrato de arrendamento foi uma prática anterior ao governo Brizola; comenta sobre discussão de como assegurar

Câmara desta Corte no dia 01.04.2014, por unanimidade, ao decidir o Processo TC nº 1340078-2, decidiu que a gestão previdenciária deve compor as contas de gestão e