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