Então agora prod/cons funciona? - IME-USP

Propaganda
Introdução à Programação Concorrente para a
Internet
Dilma Menezes da Silva
Departamento de Ciência da Computação
IME - USP
[email protected]
http://www.ime.usp.br/~dilma/jai99
Nosso Percurso
•
•
•
•
•
•
O que é Concorrência ?
Conceitos Básicos e Definições
Programação com Threads
Desafios: Corretude e Desempenho
Concorrência com Applets e Objetos Distribuídos
Modelagem e Projeto
Para que Concorrência ?
• A área é velha !! Mas a maioria estava “protegida” 
• popularidade  livros específicos … teoria + prática ?
• Aparece em BD, SO, // e Distr.
• Você precisa saber deste assunto ?
– Aplicativos para a internet
– quer aplicar teoria
– curiosidade e desafio
Apertem os cintos ...
• A home page do livro :
– já traz atualizações/correções do texto
– é um espaço para tirar dúvidas, quem sabe encontrar mais gente
“concorrente”
– contém exemplos mais interessantes
– continuará sendo atualizada com novos exemplos
• Façam perguntas !
O que é concorrência
• Sistemas concorrentes devem lidar com atividades separadas que progridam ao
mesmo tempo
• 2 atividades são concorrentes se ambas estão em um ponto
intermediário da execução
• Programação Concorrente: construção de programas contendo múltiplas atividades
que progridem em paralelo
• Como assim, executam em paralelo ? Real / Impressão
• Atividades estão relacionadas de alguma forma: (colaboram para fim
comum ou compartilham recursos)
Concorrência é relevante ?
• Às vezes é inevitável, pois sistemas recebem “estímulos” externos que
podem ocorrer simultaneamente. Ex: bancos, livraria virtual
• às vezes é desejável implementação distribuída: as coisas ocorrem
concorrentemente nos nós da rede
• alguns dos argumentos a favor de distribuição justificam também uso de
concorrência em versão centralizada
• atacar partes de um problema ao mesmo tempo
Concorrência é um “must” ???
• A divisão de uma tarefa/sistema em várias atividades que avançam ao mesmo
tempo e colaboram para atingir o resultado esperado introduz MUITAS
dificuldades algoritmicas e de implementação : É difícil !!!!
• Mas não dá para ignorar, se você mexe com:
–
–
–
–
aplicações multimídia
interfaces baseadas em janelas
servidores de informação na Teia
middleware
Aplicação Motivadora
• Corriqueiro e desvinculado de comunidades específicas
• queremos applets com vários usuários ao mesmo tempo:
– amigo secreto
– gerenciamento de agendas
– organização de festa
• Você quer colaborar com código ?
Conceitos Básicos e Definições
• Elementos ativos em uma aplicação recebem muitos nomes : atividades,
processos, tarefas, threads
• processo, troca de contexto (context switching)
• thread
• lightweight processes / pacotes de threads
Vamos nos referir a atividades concorrentes em geral como threads.
• Threads devem colaborar: (1) para atingir funcionalidade desejada ou (2) para
coordenarem-se quanto ao uso de recursos.
Conceitos Básicos (cont)
• Colaboração é feita através de comunicação
– através do uso de memória compartilhada
– através do envio de mensagens
• Comunicação exige participantes prontos nos seus postos!
EXIGE SINCRONIZAÇÃO!
• Há duas formas principais de sincronização:
– Exclusão Mútua
– Sincronização Condicional
Conceitos Básicos (cont)
• Exclusão mútua deve proteger regiões críticas
• Sincronização condicional permite que um thread espere algo
acontecer antes de prosseguir
– onde colocar a espera ?
– Que condição lógica descreve o “algo” ?
Suporte p/ Sincr. Condicional: WAIT e SIGNAL
• Alguns SOs oferecem primitivas como:
– WAIT (e) : espera pelo evento e
– SIGNAL (e): “lança” evento e
• ou algo como:
– WAIT (thread)
– SIGNAL (thread)
Thread Procura
while ( ) {
p = acha_próximo_primo();
// agora avisa!
SIGNAL (Informa);
}
Thread Informa
while ( ) {
WAIT (Procura);
envia_email_chefe(p);
}
Thread Procura
while ( ) {
p = acha_próximo_primo();
// agora avisa!
SIGNAL (Informa);
}
Programa Principal:
inteiro p;
cria_thread(Procura);
cria_thread(Informa);
roda(Procura);roda(Informa)
Thread Informa
while ( ) {
WAIT (Procura);
envia_email_chefe(p);
}
Suporte p/ Exclusão Mútua
• Objetivo: no máximo um thread ativo por vez dentro de uma região crítica
(limita concorrência)
• é importante que acesso à memória compartilhada seja protegido de acesso
simultâneo.
• Normal por aí: (1) leitura de uma posição de memória é ATÔMICA e (2) escrita
também
• Nos programas, pensamos em comandos, mas a CPU executa instruções. A
princípio, qualquer entrelaçamento dos threads pode ocorrer.
• Ex não atômico: relógios de rua de SP !
Problema de não atomicidade ...
Os threads têm três instruções:
LD x
INCR #1
(INCR #2)
ST x
Dois Históricos Possíveis:
SomaUm
LD x
INCR #1
ST x
SomaUm
LD x
INCR #1
SomaDois
LD x
INCR #2
ST x
SomaDois
LD x
INCR #2
ST x
ST x
• Em geral, não queremos que a corretude dependa do escalonamento
dos threads !!!
• O problema pode ser evitado fazendo que o comando (o thread inteiro
do exemplo) fosse encarado como uma região crítica (neste caso
curta) - região crítica longa diminui conc.
• Como dar a um thread o acesso exclusivo a uma estrutura de dados,
para um número arbitrário de leituras e gravações ? Vejamos ...
Qual é o Problema?
Garantir acesso exclusivo, e também:
• se dois disputam, pelo menos um vai ter sucesso (ausência de
deadlock)
cavalheirismo, não
• se um está tentando sozinho, nada o impedirá de conseguir
timeslot, não
• se um está tentando, alguma hora ele consegue (ausência de
starvation)
Ajuda do Hardware: Test-And-Set
Soluções envolvem protocolos. Uma idéia: testar indicador de
livre/ocupado. Por exemplo:
SomaUm
if (flag==0) {
flag = 1;
x = x + 1;
flag = 0;
}
Ajuda do Hardware: Test-And-Set
Soluções envolvem protocolos. Uma idéia: testar indicador de
livre/ocupado. Por exemplo:
SomaUm
SomaDois
if (flag==0) {
if (flag == 0) {
flag = 1;
flag = 1;
x = x + 1;
x = x + 2;
flag = 0;
flag = 0;
}
}
NÃO FUNCIONA  , veja:
SomaUm
SomaDois
LD flag
LD flag
CMP, #0
ST flag, #1
LD x
INCR #1
CMP, #0
ST flag, #1
LD x
INCR #2
ST x
LD #0
ST flag
ST x
LD #0
ST flag
TestAndSet (boolean b) {
se b indica região livre {
mude b para indicar ocupação;
pule próxima instrução;
} senão execute próxima instrução;
Outra forma disponível: TestAndSet(valorLido,flag)
Variável flag compartilhada (inicializada com 0)
A entrada na região crítica fica:
valorLido = 1;
while (valorLido == 1)
TestAndSet(valorLido, flag); // atômico
if (valorLido == 0) {
região crítica;
flag = 0;
}
}
• Compare-And-Swap
• Desvantagens:
– Este tipo de solução pode conflitar com gerenciamento de cache, degradando
desempenho
– não garante justiça
• Outra solução auxiliada por hardware: desabilitar interrupções
E se o hardware não ajuda?
• ~1965: soluções de Dijkstra, Dekker. Problema não era considerado
muito interessante até multiprocessamento com memória
compartilhada estivesse em evidência
• algoritmos para o problema não são simples  …
Ticket Algorithm
• Baseado no proc. dos açougues/confeitarias “chiques”
int minha_vez;
// variável interna ao thread
minha_vez = número;número++;
while (próximo != minha_vez); // busy waiting
executa_região_crítica();
próximo++;
Ticket Algorithm
int minha_vez;
// variável interna ao thread
minha_vez = número;número++;
// ATÔMICO!
while (próximo != minha_vez); // busy waiting
executa_região_crítica();
próximo++;
// ATÔMICO
• overflow
• precisamos de pedaços com exclusão mútua!!!
Bakery Algorithm
• Não exige gerador atômico de próximo número e nem a manutenção
de um próximo
• Intuição: nossas idas a padarias, espera por banheiro, etc
• thread escolhe, ao chegar, número estritamente maior do que dos
outros que esperam
vez[thr] = máximo(vez) + 1;
while ( vez[thr] > mínimo_não_nulo(vez)
);
executa_região_crítica();
vez[thr] = 0; // indica que não quer mais região crítica
Usamos Tie Breaker ...
while (
vez[thr] > mínimo_não_nulo(vez)
);
Isso vai funcionar usando Tie Breaker para implementar a escolha do mínimo de vez (desempate
com id do thread) :
para todo thread j que não thr {
while ( vez[thr] != 0 && (vez[thr], thr) > (vez[j], j) );
}
• Idéia: n threads, n fases, em cada fase só passa 1
• preciso saber destes algoritmos ? Vou usar ?
Semáforos
• Mecanismo para sincronização de atividades concorrentes
• tipo abstrato de dados
• usualmente representado por int e fila dos que estão aguardando para
“sincronizar”
• Usos disponíveis:
– pode receber valor inicial de máximo número de threads que podem “passar”
ao mesmo tempo
– wait
– signal
(P)
(V)
Semáforo lock = new Semáforo(1); // compartilhada
lock.wait();
executa_região_crítica();
lock.signal();
// comandos fora da região crítica
• Desvantagem: são globais, sem relação c/ recurso protegido
Monitores
• Módulos de programas que têm a estrutura de um tipo abstrato de dados: dados
encapsulados e operações definidas no módulo
• implementação do monitor assegura exclusão mútua entre operações do módulo
• Mas nem só de exclusão mútua vive a progr concorrente: monitores
fornecem sincr. condicional
• variáveis de condição, com wait() e signal(). Suscetível a erros: se o
programador esquece wait ou signal ...
Vimos isso que não funciona:
Thread Procura
while ( ) {
p = acha_próximo_primo();
// agora avisa!
SIGNAL (Informa);
}
Thread Informa
while ( ) {
WAIT (Procura);
envia_email_chefe(p);
}
Com monitores:
Monitor Primos {
condição
primo_encontrado;
operação
procura_próximo() {
acha_próximo_primo();
primo_encontrado_signal();
}
operação informa_próximo() {
primo_encontrado_wait();
envia_email_para_chefe();
}
}
Thread Procura {
while (true) {
Primos.procura_próximo();
}
}
Thread Informa {
while (true) {
Primos.informa_próximo();
}
}
Há um problema em potencial ...
Monitor PrimosComLoop {
condição
primo_encontrado;
operação
procura() {
while (true) {
acha_próximo_primo();
primo_encontrado_signal();
}
}
operação informa_próximo() {
while (true) {
primo_encontrado_wait();
envia_email_para_chefe();
}
}
}
Há um problema em potencial ...
Monitor PrimosComLoop {
condição
primo_encontrado;
operação
procura() {
while (true) {
acha_próximo_primo();
primo_encontrado_signal();
}
}
operação informa_próximo() {
while (true) {
primo_encontrado_wait();
envia_email_para_chefe();
}
}
}
Aqui ativo!!
Até agora ...
• O que é Concorrência ?
• Conceitos Básicos e Definições
Nosso Percurso
•
•
•
•
•
O que é Concorrência ?
Conceitos Básicos e Definições
Programação com Threads
Desafios: Corretude e Desempenho
Concorrência com Applets e Objetos Distribuídos
• Modelagem e Projeto
Programação com Threads
• Vimos até agora para lidar com sincronização:
–
–
–
–
suporte do hardware
algoritmos de exclusão mútua
semáforos / locks
monitores
• Programar com threads é considerado por alguns demasiadamente complexo (Ousterhout);
outros acham que um uso disciplinado de threads pode ser aprendido e empregado
• Hoje em dia, se programa em Threads com Java e Pthreads
Pthreads
• Oferece:
– criação, cancelamento, ajuste de prioridade
– mutexes
– variáveis de condição
• Definição completa padrão IEEE POSIX 1003.4a
• Ex de impl: Solaris Threads, DECthreads,user level
• essencialmente C com rotinas de manipulação de threads. Problemas
com memória compartilhada.
Java Threads
• Java, além da especificação de uma sintaxe e semântica para
especificação de programas, propicia pacotes prontos.
• Orientada a objetos, interpretada, “arquitetura neutra”/portável,
dinâmica e distribuída, robusta, segura, multithreaded
• unidade fundamental de programação é a classe
Definindo threads
public class NossoThread extends Thread {
public void run ( ) {
System.out.println (“Oi!”);
}
}
public class UsaNossoThread {
public static void main (String [] args) {
new NossoThread().start();
}
}
Com vários threads
class NossoThread extends Thread {
public NossoThread (String nome) {
super(nome);
}
public void run ( ) {
System.out.println(“exec. “ + getName() + “- Oi!”;
}
public class UsaNossoThread {
public static void main (String [] args) {
new NossoThread(“Um”).start();
new NossoThread(“Dois”).start();
new NossoThread(“Três”).start();
}
}
Com interface runnable
class NossoDizOi implements Runnable {
public void run ( ) {
System.out.println(“Exec. Thread “
+ “ Oi!”);
}
agora o programa …
public class UsaNossoThread {
public static void main (String [] args) {
int num_thr ;
NossoDizOi codigo;
num_thr = Integer.parseInt(args[1]);
for (int j=0; j < num_thr; j++) {
codigo = new NossoDizOi();
new Thread(codigo, j).start();
}
}
}
for (int j=0; j < num_thr; j++) {
codigo = new NossoDizOi();
new Thread(codigo, j).start();
}
pode ficar:
Faço com java.lang.Thread ou java.lang.Runnable ?
• Se a classe que você está criando com o código a ser executado precisa
ser derivada de alguma outra classe, então utilize Runnable.
– Exemplo: applets
Nosso exemplo como Applet
Rótulo, Área de Texto, Botão
Nosso exemplo como Applet
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
public class NossoApp extends Applet implements
Runnable {
Label label_saida = new Label (“Saida: “);
TextArea texto_saida = new TextArea(“”, 10, 50);
Button botao = new Button (“Executar”);
public void run ( ) {
texto_saida.append(“Exec “
+ Thread.currentThread().getName()
+ “- Oi!\n”);
}
public void init ( ) {
add(label_saida);
add(texto_saida);
add(botao);
addActionListener(
);
}
void cria_threads ( ) {
new Thread(this, “Um”).start ( );
new Thread(this, “Um”).start ( );
new Thread(this, “Um”).start ( );
}
} // fim da classe
Prioridades
•
•
•
•
Threads são escalonados em alguma ordem
Java adota escalonamento c/ prioridades fixas
thread começa c/ prior. de quem o criou
alterações via método setPriority
• ctes em java.lang.Thread: MIN_PRIORITY, MAX_PRIORITY
Entra e Sai da CPU
Um thread escolhido continua executando até que:
• algum thread de maior prioridade fique pronto para executar
• seu método run ( ) termine, ou ele ceda a vez
• para ambiente de execução que propicia fatias de execução (ex
Windows 95/NT ), a cota terminou
• Não há garantia de que o thread de maior prioridade vá estar rodando
(avoiding starvation)
• prioridades são úteis para incentivar threads a progredirem, mas pode
ser necessário desocupar a CPU periodicamente
Cedendo a vez
• Em simulações com visualizações é importante que tanto os elementos simulados quanto a
visão da simulação recebam CPU, mas se damos prioridade para um, este pode ficar sentado
na CPU!
• yield() cede a vez para outro de mesma prioridade
• static void sleep (long millis)
sleep (long millis, int nanos)
static void
• interrupt()
Sincronização
• A maioria das aplicações exige que threads compartilhem informação e considerem o estado e
as atividades de outros threads antes de progredir
• Java permite a definição de regiões críticas (pedaços em que um
mesmo obj é acessado por threads concorrentes)
– métodos ou blocos de comandos
• implementação Java associa a cada objeto um lock
Produtor-Consumidor
• Aplicação formada por um thread que de vez em quando produz
números, e dois threads que consomem os números gerados
• prod/consumidores compartilham info
• recurso: repositório de números produzidos
Chute inicial
public class ProdutorConsumidor {
private Produtor prod;
private Consumidor cons1, cons2;
private Repositorio reposit;
ProdutorConsumidor (int num) {
reposit = new Repositorio (num);
prod = new Produtor(
);
int num_cada = num/2;
cons1 = new Consumidor (
);
if (num % 2 != 0) num_cada++;
cons2 = new Consumidor (
);
new Thread(prod, “Produtor”).start();
new Thread (cons1, “Consumidor1”).start();
new Thread (cons2, “Consumidor2”).start();
}
public static void main (String[ ] args) {
ProdutorConsumidor prog = new ProdutorConsumidor(10);
}
}
Classe Produtor
class Produtor implements Runnable {
private Repositorio reposit;
private int num_elementos;
Produtor ( int num, Repositorio reposit ) {
num_elementos = num;
this.reposit = reposit;
}
public void run ( ) {
for (int I=0; I < num_elementos; I++)
int numero = (int) (Math.random() *1000);
reposit.poe(numero);
System.out.println(“Produtor colocou “ + numero);
}
}
}
Classe Consumidor
class Consumidor implements Runnable {
private Repositorio reposit;
private int soma = 0, num_elementos;
Produtor ( int num, Repositorio reposit ) {
num_elementos = num;
this.reposit = reposit;
}
public void run ( ) {
int j = 0;
while (j < num_elementos) {
if (!reposit.vazio ( ) ) {
int numero = reposit.tira( );
j++; soma+=numero;
System.out.println(Thread.currentThread().getName()
+ “ tirou “+numero +“. Media “ + (double) soma/j);
}
}
}
}
Classe Repositorio
class Repositorio {
int num_elementos;
int in = 0, out =0;
int [] vetor;
Repositorio (int num) {
num_elementos = num;
vetor = new int[num];
}
void poe (int num) {
vetor [in++] = num;
}
boolean vazio ( ) {
if ( in == out)
return true;
else
return false;
}
int tira ( ) {
return (vetor[out++]);
}
}
um escalonamento possível
Cria prod,cons1,cons2
insere
vazia?não
vazia?não
retira
retira
PRECISAMOS DELIMITAR USO DO RECURSO
o que deve rodar exclusivo ?
•
•
•
•
Remoção: no máximo uma de cada vez
inserção: idem
{vazia,vazia} estaria ok
vazia com retira ou com põe ?
Thread cons1
Thread cons2
/*começa a executar vazia
(suponha 1 elemento lá) */
in == out ?? Não
/*chama vazia*/
in == out ?? Não
retorna false
out++ e retorna elem/o
retorna false
out++ e que elemento ?
Sychronized
class Repositorio {
synchronized void poe (int num) { … }
synchronized boolean vazia ( ) { … }
synchronized int tira () { … }
}
• Em java, todo objeto está implicitamente associado a um lock. A execução segue
protocolo usual de obtenção de lock
• neste exemplo, monitor
Suporte p/ sincr. reentrante
public class QueAcontece {
public synchronized void a ( ) {
b ( );
System.out.println(“Em a()”);
}
public synchronized void b ( ) {
System.out.println(“Em b()”);
}
}
new QueAcontece().a() resulta em
Em a()
Em b()
Então agora prod/cons funciona?
class Consumidor implements Runnable {
private Repositorio reposit;
private int soma = 0, num_elementos;
Produtor ( int num, Repositorio reposit ) {
num_elementos = num;
this.reposit = reposit;
}
public void run ( ) {
int j = 0;
while (j < num_elementos) {
if (!reposit.vazio ( ) ) {
int numero = reposit.tira( );
j++; soma+=numero;
System.out.println(Thread.currentThread().getName()
+ “ tirou “+numero +“. Media “ + (double) soma/j);
}
}
}
}
Classe Repositorio
class Repositorio {
int num_elementos;
int in = 0, out =0;
int [] vetor;
Repositorio (int num) {
num_elementos = num;
vetor = new int[num];
}
synchronized void
poe (int num) {
vetor [in++] = num;
}
synchronized boolean vazio ( ) {
if ( in == out)
return true;
else
return false;
}
synchronized int tira ( ) {
while (vazio()) {
< dorme um pouquinho >;
}
return (vetor[out++]);
}
}
Classe Repositorio
class Repositorio {
int num_elementos;
int in = 0, out =0;
int [] vetor;
Repositorio (int num) {
num_elementos = num;
vetor = new int[num];
}
synchronized void
poe (int num) {
vetor [in++] = num;
}
synchronized boolean vazio ( ) {
if ( in == out)
return true;
else
return false;
}
synchronized Integer tira
(){
if (vazio()) {
return null;
}
else {
num = new Integer(vetor[out++]);
return true;
}
}
}
class Consumidor implements Runnable {
private Repositorio reposit;
private int soma = 0, num_elementos;
Produtor ( int num, Repositorio reposit ) {
num_elementos = num;
this.reposit = reposit;
}
public void run ( ) {
int j = 0;
Integer num;
while (j < num_elementos) {
if ( ( valor = seDertira ( ) ) != null ) {
j++; soma+=num;
System.out.println(Thread.currentThread().getName()
+ “ tirou “+numero +“. Media “ + (double) soma/j);
}
}
}
}
Então agora prod/cons funciona?
Três threads são criados por ProdutorConsumidor:
Prod
cons1
cons2
i < num_elem ?
!reposit.vazio() ?
i < num_elem ?
!reposit.vazio() ?
Poderíamos dar mais prioridade ao thread certo …
ou fazer consumidor passar a ver por própria vontade:
yield()
while ( i < num_elementos ) {
if (!reposit.vazio) ) {
int numero = reposit.tira();
soma += numero;
i++;
System.out.println( “ … “);
}
}
Exclusão mútua de trechos
Object objeto = …; // algum objeto
synchronized (objeto) {
}
Sincronização condicional
• Desejamos que um thread fique bloqueado até que uma condição seja
verdadeira
• ESPERE_ATE_QUE (expr booleana qualquer)
– não é viável implementar
• forma mais limitada: wait e notify
• método wait pode ser chamado por qualquer objeto, mas o thread executando
tem que estar de posse do lock do objeto (senão, exception lançada)
• resultado do wait(): faz thread aguardar até notify()
• notify() termina com a espera. Tendo vários aguardando o lock, um
arbitrariamente é escolhido
• notifyAll(): todos
• notify()/notifyAll() com ninguém na esperas: ok
• wait (long timeout) e
wait
(long timeout, int nanos)
Classe Produtor
class Produtor implements Runnable {
private Repositorio reposit;
private int num_elementos;
Produtor ( int num, Repositorio reposit ) {
num_elementos = num;
this.reposit = reposit;
}
public void run ( ) {
for (int I=0; I < num_elementos; I++)
int numero = (int) (Math.random() *1000);
reposit.poe(numero);
System.out.println(“Produtor colocou “ + numero);
}
}
}
Class Consumidor
class Consumidor implements Runnable {
private Repositorio reposit;
private int soma = 0, num_elementos;
Produtor ( int num, Repositorio reposit ) {
num_elementos = num;
this.reposit = reposit;
}
public void run ( ) {
int j = 0;
Integer num;
while (j < num_elementos) {
if ( reposit.tira ( num) ) {
j++; soma+=num;
System.out.println(Thread.currentThread().getName()
+ “ tirou “+numero +“. Media “ + (double) soma/j);
}
}
}
}
Nova classe Repositorio
class Repositorio {
int num_elementos;
int in = 0, out =0;
int [] vetor;
Repositorio (int num) {
num_elementos = num;
vetor = new int[num];
}
synchronized boolean vazio ( ) {
if ( in == out)
return true;
else
return false;
}
synchronized int tira ( ) {
if (vazio())
wait();
return vetor[out++];
}
}
}
novo Consumidor
class Consumidor implements Runnable {
private Repositorio reposit;
private int soma = 0, num_elementos;
Produtor ( int num, Repositorio reposit ) {
num_elementos = num;
this.reposit = reposit;
}
public void run ( ) {
int j = 0;
while (j < num_elementos) {
int numero = reposit.tira( );
j++;
System.out.println(Thread.currentThread().getName()
+ “ tirou “+numero +“. Media “ + (double) soma/j);
}
}
}
}
Método join
• Quando thread t1 executa t2.join(), o thread fica bloqueado até que a
execução de t2 termine
• se t2 (thread alvo) é interrompido, t1 fica sabendo (via interruption)
• join (long timeout)
• isAlive()
Grupos de Threads
• Coleção de threads permite manipular vários threads ao mesmo tempo
• importante para segurança
• todo thread pertence a algum grupo
– grupo main
– grupo do applet (depende de como o browser faz)
• ThreadGroups podem ser membros de grupos (estruturação
hierárquica)
daemons
• setDaemon especifica que o thread deve terminar quando todos os
outros threads (não deamons) terminam
• deve ser chamado antes do método start
Parando threads: exemplo antigo
public class PiscaPisca extends ParteGrafica
implements Runnable {
PiscaPisca ( ) { super( ) }
public void run ( ) {
while (true) {
inverte();
repaint();
< dorme um pouquinho >
}
}
Para usar este PiscaPisca, o cliente cria o objeto e coloca para executar.
Se quisesse cessar temporariamente a ativdade do objeto, poderia até o
Java 1.1 fazer:
obj.stop()
stop deprecated
• stop( ) inerentemente perigoso: libera todos os locks, deixando o
potencial para recursos ficarem inconsistentes
– difícil detectar e corrigir
• recomendação para conseguir parar o trabalho do thread: usar variável
indicando o que deve ser feito, e ter o thread checando-a
frequentemente
• variável dever ser volatile
PiscaPisca em Java 1.2
public class PiscaPisca extends ParteGrafica
implements Runnable {
PiscaPisca ( ) { super( ) ;
}
public void run ( ) {
while (
inverte();
repaint();
<dorme um pouquinho >
}
){
public void para ( ) {
quempisca = null;
}
}
Efeito pára/continua
• Até 1.1, estavam disponíveis:
– suspend()
– resume()
• inerentemente perido de deadlock: suspende mantém locks, e era
comum tentar obter lock antes de chamar resume( )
• recomenda-se obter o efeito de desativação via wait/notify
private volatile boolean threadSuspenso = false;
public void suspende( ) { threadSuspenso = true;}
public void continua() { threadSuspenso = false;}
public void run ( ) {
while (true) {
try {Thread.currentThread.sleep(100);
while (threadSuspenso) { wait ( ) ; }
}
catch (InterruptedException e) { }
}
Inconveniente: requer synchronized (this) toda hora
Vamos pegar lock, só com chance de precisar mesmo:
if (threadSuspenso) {
synchronized (this) {
wait( ) ;
}
}
Desafios: Corretude e Desempenho
• Dois aspectos de prevenção relativos a interferência entre threads,
duas propriedades correspondentes desejáveis
• safety: a propriedade de que nada “ruim” ocorre
• liveness: a propriedade de que alguma hora, algo “bom” acontece
• falhas de segurança  funcionamento errôneo
• falhas de vivacidade  pedido do usuário/sistema nunca chega a ser
executado
• Em engenharia, usualmente se enfatiza primeiro safety. Por outro lado,
a maior parte do tempo gasto nos progs concorrentes está relacionado
a aspectos de eficiência e liveness
• grande desafio é ter os dois aspectos bem trabalhados
• Safety: Ausência de deadlock e exclusão mútua no acesso
compartilhado a recursos
• Liveness: garantia de que o requisito por um serviço será alguma hora
atendido (ex: um thread esperando pelo acesso a região crítica
• Provar propridades de safety:
– modelar o programa em termos de ações baseadas em estados
– provar que as ações executadas preservam as propriedades desejadas:
predicados correspondentes sobre o estado se mantem válidos
• Provas propriedades de liveness: garantir que a política de
escalonamento é justa
• definições relacionadas a justiça:
– incondicionamente justa
– justiça fraca
– justiça forte
• estratégias de desenvolvimento que resultam em segurança
– imutabilidade (variáveis final)
– sincronização (total ou parcial, limitando concorrência
– agregação
(compartilhamento hierárquico)
• Problemas de liveness são bem mais difíceis de serem detectados
– falha devido a perder sucessivamente a disputa por um recurso compartilhado
– dormência (wait com notify nunca gerado)
– deadlock
• prevenir deadlocks: ordenar variáveis de condição
– 2PL
Concorrência na Internet
• Applets migram para o cliente, com limitações no que consegue fazer
• aplicações motivadoras precisam de um recurso compartilhado que
não pode migrar
• colocar synchronized em um método não protege este tipo de recurso
na concorrência via browsers
• o recurso deve ficar no “servidor” e lá o acesso concorrente ser
controlado
• servlets
Exemplo de acesso remoto RMI
via applet
• partes:
– SomaUm.html
– SomaUmApplet.java
– objeto servidor via RMI:
SomaUmImpl.java, policy
SomaUm.java,
SomaUm.html
<html>
<head> <title>SomaUm</title></head>
<body>
<center><h1>SomaUm</h1></center>
<applet codebase=“executaveis/”
code = “SomaUmApplet”
width = 500 height=200>
</applet> </html>
SomaUmApplet
SomaUmApple.java
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
import java.rmi.Naming;
import java.rmi.RemoteException;
public class SomaUmApplet extends Applet implements Runnable {
String mens = “blank”;
int num_it = 0;
SomaUm obj = null;
Label label = new Label (“Number of Iteractions”);
TextField texto = new TextField(“10”, 12);
Button b = new Button(“Run!”);
public void run ( ) {
long delta0 = System.currentTimeMillis( );
for (int I=0; I < num_it; I++ ) {
try {
mens = obj.incr( );
repaint( );
} catch (Exception e) {
System.out.println(“Deu pau no rmi”);
e.getMessage( );
e.printStackTrace( );
}
}
long total = System.currentTimeMillis( ) - delta0;
mens = “Time: “ + total + “msec”;
repaint( );
}
public void init ( ) {
final SomaUmApplet thisapplet = this;
add(label);
add(texto);
add(b);
resize(500, 200);
b.addActionListener ( new ActionListener ( ) {
Graphics g = getGraphics ( ) ;
g.drawString (“
String numstr = texto.getText( ) ;
int num = 0;
boolean gotvalue = true;
try { num = Integer.parseInt(numstr); }
} catch (NumberFormatException exc) {
gotvalue = false;
g.setColor( Color.red ) ;
g.drawString (“Numero inválido”);
g.setColor(Color.black);
}
if (gotvalue) {
“, 30, 60);
}
thisapplet.num_it = num;
new Thread(thisapplet).start ( );
} } ); }
public void start ( ) {
try {
obj = (SomaUm) Naming.lookup(“//”
+getCodeBase( ).getHost( )
+”SomaUmServer”);
} catch (Exception e) {
System.out.println(“ …”);
e.printStackTrace( ) ;
}
}
}
SomaUm.java
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface SomaUm extends Remote {
String incr ( ) throws RemoteException;
}
SomaUmImpl.java
Import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.RMISecurityManager;
import java.rmi.server.UnicastRemoteObject;
import java.io.*;
import java.lang.*;
public class SomaUmImpl extends UnicastRemoteObject implements SomaUm {
int num = 1;
public SomaUmImpl ( ) throws RemoteException {
super ( ) ;
}
public String incr ( ) {
num++;
String filename = new String (“/……/data”);
FileWriter out = null;
try { out = new FileWriter(filename, true); }
catch (IOException e) { … }
catch (SecurityException e) { … }
try { out.write( numst, 0, numst.length() ); }
catch (IOException e) { … }
return “Agora tem “ + num;
}
public static void main (String[ ] args) {
if (System.getSecurityManager ( ) == null) {
System.setSecurityManager (new
RMISecurityManager( ) );
}
try {
SomaUmImpl obj = new SomaUmImpl ( );
Naming.rebind(“//minha.maquina.usp.br/SomaUmServer”,
obj); /* porta default */
} catch (Exception e) { .. }
}
Policy (gerado policytool)
/* DO NOT EDIT */
grant {
permission java.io.FilePermission
“……./data”;
permition java.net.SocketPermission
“minha.maquina.usp.br”,
“accept, connect, listen,resolve”;
};
Modelagem e Projeto
• Em geral, ad hoc
• dois enfoques na literatura:
– DFDs expressando transformações
– ELM (entity-life modeling):
• identifica eventos
• atribui eventos a threads: cria linhas de eventos de forma que cada evento pertença a
exatamente uma linha
• modelagem de threads minimal: em todo instante, cada thread está lidando com um
thread
• padrões de projeto
Download