Escolar Documentos
Profissional Documentos
Cultura Documentos
Distribuída
® MTOV 2
Programação Concorrente (cap. 1)
® MTOV 3
Programação Concorrente
® MTOV 4
Conceitos e Arquiteturas
® MTOV 5
Multiprocessadores com
Memória Compartilhada
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 compartilhada ou troca
de mensagens
Vantagem: barramento de alta velocidade,
memória compartilhada facilita programação
Desvantagem: limitação no número de nodos
® MTOV 6
Multiprocessadores com
Memória Distribuída
® MTOV 7
Clusters
® MTOV 8
Multiplicação Matrizes
(Memória Compartilhada)
Multiprocessadores com memória compartilhada
Idéia: calcular produtos internos paralelamente
Variáveis compartilhadas:
double a[n,n], b[n,n], c[n,n]
Código da multiplicaçã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
c[i,j] = c[i,j] + a[i,k] * b[k,j]
® MTOV 9
Multiplicaçã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 initial 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];
initialize 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;
}
® MTOV 10
IPC via Unix Pipes
® MTOV 11
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)
printf (“Erro na criação do pipe”);
else ((pid = fork()) < 0)
printf (“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
}
® MTOV 12
Processos e Sincronização (cap. 2)
® MTOV 13
Sincronização
® MTOV 14
Safety e Liveness
® MTOV 15
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 condition: 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 pattern in line;
if (pattern is in line)
write line;
read next line of input;
}
® MTOV 16
Exemplo: grep paralelo
® MTOV 17
Sincronização
® MTOV 18
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:
® MTOV 19
Sincronizando Produtores e
Consumidores
Copiar um vetor de inteiros “a” de um processo
para outro, usando um buffer compartilhado
Buffer é uma único inteiro “buf” (compartilhado)
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;
}
}
Sincronização via await implica em uma espera
ocupada (“busy wait”)
® MTOV 20
Políticas de Escalonamento
® MTOV 21
Locks e Barreiras (cap. 3)
® MTOV 22
Seção Crítica via Locks
® MTOV 23
Seção Crítica 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;
while(!B) { CSExit; delay; CSEnter; }
S;
CSExit;
® MTOV 24
Algoritmo Tie-Breaker
® MTOV 25
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 último)
– P2 saiu e disputa SC (in2= true; last= 2)
– P1 entra (já que last==2)
® MTOV 26
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); >
critical section;
< next++; >
non-critical section;
}
}
Depende de uma instrução FA (fetch and add):
– FA(x, incr)= < int t= x; x=x+incr; return t; >
Alternativa: usar “test and set” p/ implementar FA
® MTOV 27
Barreiras
® MTOV 28
Barreiras com Processo
Coordenador
Evita contenção no acesso à memória
– “arrive[i]”: indica se processo i atingiu barreira
– “continue[i]”:indica pode prosseguir execução
Algoritmo:
int arrive[n], continue[n]; // inicializados com zero
process Worker[i=1 to n] {
while (true) {
code to implement task i;
arrive[i]=1; // anuncia chegada
< await (continue[i]==1); > // permissão prosseguir
continue[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] continue[i]=1; // avisa podem prosseguir
}
}
Quem testa um flag, deve resetá-lo
® MTOV 29
Semáforos (cap. 4)
® MTOV 30
Semáforos
® MTOV 31
Produtor/Consumidor
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
}
}
Semáforos particionados (split): um processo executa
P(s);...;V(r) e outro processo P(r);...;V(s)
® MTOV 32
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 existir slot vazio: P(empty)
Após produzir, um slot ficou ocupado: V(full)
Antes consumir, deve existir slot ocupado: P(full)
Após consumir, um slot ficou vazio: V(empty)
® MTOV 33
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 multiple producers to access buffer
buf[rear]=data; rear=(rear+1)%n;
V(mutexP); # leave critical section
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 multiple consumers to access buffer
result=buf[front]; front=(front+1)%n;
V(mutexC); # leave critical section
V(empty); # increment empty counter - split semaphore
“consume result”
}
}
® MTOV 34
Jantar dos Filósofos
® MTOV 36
Leitores e Escritores
® MTOV 37
Leitores e Escritores
® MTOV 38
Monitores (cap. 5)
® MTOV 39
Monitores
® MTOV 40
Exemplo 1: Semáforos
® MTOV 41
Exemplo 2:
Produtor/Consumidor
monitor Buffer {
int buf[n]; # buffer
int primeiro=0, # primeira posição ocupada em buf
ultimo=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
® MTOV 42
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 (InterruptedException e) { return; }
}
public static 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 static void main(String [] args) {
Runnable ping= new PingPong("ping");
Runnable pong= new PingPong("PONG");
new Thread(ping).start();
new Thread(pong).start();
}
}
® MTOV 43
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
® MTOV 45
Sincronização
Como “acordar” uma thread? notify() ou notifyAll()
notify(): 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
notifyAll(): 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.
– notify(): move uma thread arbitrária do conjunto de
espera para o conjunto de entrada
– notifyAll: move todas as threads do conjunto de
espera para o conjunto de entrada
Quando uma thread libera o lock de um objeto, uma
thread arbitrária do conjunto de entrada é desbloqueada
® MTOV 46
Produtor/Consumidor em Java
® MTOV 47
Produtor/Consumidor em Java
® MTOV 48
Leitores e Escritores
® MTOV 49
Comando synchronized
® MTOV 50
Jantar dos Filósofos em Java
aliceAccount bobAccount
transfer()
transfer()
® MTOV
withdraw()
deposit() ? withdraw()
deposit()
52
Deadlocks: Exemplo 2
® MTOV 53