PR UNIVERSIDADE TECNOLÓGICA FEDERAL DO PARANÁ PROGRAMAÇÃO PARALELA E DISTRIBUÍDA Fundamentos Prof. Cesar Augusto Tacla http://www.dainf.ct.utfpr.edu.br/~tacla JAVAProgParSD/0030-ProgParalelaDistribuida.ppt Sumário 1. Introdução a. b. c. d. e. f. g. Definição de sistemas paralelos e distribuídos Conceitos: programa, processo e thread Criação de threads Yield, Sleep, Join Prioridade entre threads Compartilhamento de memória Máquina de estados de uma thread 2. Problemas de concorrência a. Race condition b. Vivacidade (deadlock, livelock, starvation) c. Soluções (mutex, semáforos, monitor) Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla 2 REFERÊNCIAS BIBLIOGRÁFICAS (Coulouris et al., 2003) George Coulouris, Jean Dollimore, Tim Kindberg. Distributed Systems - Concepts and Design, Addison Wesley Publ. Comp., 3a edição, 2003. (Tanenbaum & Steen, 2002) Andrew S. Tanenbaum, Maarten van Steen. Distributed Systems, Prentice-Hall International, 2002. (Garg, 2004) Vijay K. Garg. Concurrent and Distributed Computing in Java, Wiley Inter-Science, 2004, 309p. (LEA, 200) Douglas Lea. Concurrent programming in Java: design principles and patterns, 2nd ed. Reading: Addison-Wesley, c2000. 411 p. (Java series). 3 Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla Sumário 1a Definição de sistemas paralelos e distribuídos Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla 4 Sistema Paralelo Múltiplos processadores conectados por uma memória compartilhada Memória Compartilhada barramento CPU CPU CPU CPU MEM LOCAL MEM LOCAL MEM LOCAL MEM LOCAL 5 Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla Sistema Distribuído Múltiplos processadores conectados por uma rede de comunicação Troca de mensagens Rede de Comunicação Segundo (Garg, 2004), são sistemas compostos por múltiplos processadores conectados por uma rede de comunicação, sendo a rede de comunicação uma LAN (Ethernet) ou WAN (Internet). Neste tipo de sistema os processadores se comunicam por troca de mensagens (não por memória compartilhada) Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla 6 Necessidades ◊ Sistemas paralelos e distribuídos Necessitam de ferramentas e técnicas Distintas das utilizadas em software sequencial Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla 7 Distribuído x Paralelo (1) ◊ Em relação ao hardware, Se pensarmos em desempenho, é melhor termos sistemas distribuídos ou paralelos? R: combinação Computadores multiprocessados conectados por uma rede Rede de Comunicação Memória Compartilhada CPU CPU CPU CPU MEM LOCAL MEM LOCAL MEM LOCAL MEM LOCAL Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla 8 Distribuído x Paralelo (2) ◊ Por que não sistemas completamente paralelos? Escalabilidade Modularidade e heterogeneidade Compartilhamento de dados Compartilhamento de recursos Estrutura distribuída por si Confiabilidade Baixo custo 9 Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla Distribuído x Paralelo (3) ◊ Por que não sistemas completamente distribuídos? Melhor desempenho no processamento local É mais rápido atualizar uma memória local do que enviar uma mensagem para outro processador, especialmente quando o valor de uma variável deve ser comunicado para vários processadores Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla 10 Distribuído x Paralelo (4) ◊ Em relação ao software (API programação), Pode ser independente do hardware? ◊ Na realidade, reflete a estrutura de hardware Modelo de objetos distribuídos (ex. RMI) tem-se Processos que se comunicam por mensagens Processos que podem ter várias threads Threads que se comunicam por memória compartilhada Rede de Comunicação Memória Compartilhada CPU CPU CPU CPU MEM LOCAL MEM LOCAL MEM LOCAL MEM LOCAL Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla 11 Sumário 1b Conceitos: programa, processo e thread Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla 12 Programa, processo, thread ◊ Programa conjunto de instruções numa linguagem de alto nível ou de máquina ◊ Processo resulta da execução do programa, tem um ambiente de execução autocontido e privado. O que o usuário enxerga como aplicação ou sistema, normalmente é composto por vários processos Sequencial = 1 processo Concorrente = vários processos Processo 1 programa execução Processo 2 Processo n Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla 13 Programa, processo, thread ◊ Thread são chamadas de processos peso-pena ◊ Tanto threads como processos provêm um ambiente de execução ou contexto de execução ◊ Toda aplicação possui pelo menos uma thread main ◊ Threads compartilham o mesmo "espaço de endereçamento“ (memória) ◊ É mais rápido chavear a execução entre threads do que entre processos. ◊ Threads compartilham recursos do processo: memória e arquivos abertos. Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla 14 Programa, processo e thread ◊ Uma thread recebe recursos próprios durante a execução: uma pilha (stack) de execução para poder chamar métodos, passar parâmetros, alocar variáveis locais um "Program Counter" Estes elementos formam o contexto de execução da thread 15 Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla Programa, processo, thread Stack: armazena o estado de cada invocação de método: variáveis locais, argumentos, retorno e dispatch de exceções. São vários frames empilhados stack overflow Heap: armazena instâncias de objetos criadas em tempo de execução. Também armazena arrays OutOfMemoryError Processos que compartilham código e dados são chamados de threads, são também chamados de processos lightweight Program Counter Register PCR PCR PCR Stack Thread 1 Stack Thread 2 Stack Thread 3 Contexto de execução de uma thread heap Method area Compartilhado pelas threads Method area: informações da classe (nome, modificadores), superclasse, atributos e seus modificadores, atributos estáticos com valores, métodos e seus modificadores, bytecodes Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla 16 Exemplos de threads ◊Threads – demos Links na página do curso http://www.doc.ic.ac.uk/~jnm/concurrency/classes/ThreadDemo/ThreadDemo.html http://www.dsc.ufcg.edu.br/~jacques/cursos/map/html/threads/threads1.html observar como uma applet é incorporada a uma página WEB <applet codebase="Sort/" code="SortItem.class" width="300" height="300"> Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla 17 Sumário 1c Criação de Processos/Threads Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla 18 UNIX, C++ ◊ Criação de processos Processo pai programa pid = fork(); if (pid == 0) { cout << “processo filho”; else cout << “processo pai”; pid = fork(); if (pid == 0) { cout << “processo filho”; else cout << “processo pai”; Processo filho pid = fork(); if (pid == 0) { cout << “processo filho”; else cout << “processo pai”; 19 Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla Criação de threads em Java ◊ Há 2 métodos Extends Thread Implements Runnable Thread Runnable <<realizes>> MinhaThread +run():void MinhaThread +run():void Possibilita estender outra classe Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla 20 Criação de threads em Java ◊ Exemplo de criação de Thread por extends Diagrama de sequência em UML public class MinhaThread extends Thread { public void run() { System.out.println(“Ola!!!!”); } } :Main new :MinhaThread start() run() public class Main { public static void main(String[] args) { MinhaThread t = new MinhaThread(); t.start(); } } 21 Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla Criação de threads em Java ◊ Exemplo de criação de Thread por implements public class MinhaThread implements Runnable { public void run() { System.out.println(“Ola!!!!”); } } public class Main { public static void main(String[] args) { MinhaThread t = new MinhaThread(); Thread t2 = new Thread(t); t2.start(); } } Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla :Main new :MinhaThread start() run() 22 Criação de threads em Java ◊ Exemplo básico: threads contadoras Código fonte e .jar disponível em http://www.dainf.ct.utfpr.edu.br/~tacla/JAVARepositorio/JThreads/JThreadContadoras Duas threads são criadas, cada uma conta até 1.000. Os resultados podem aparecer intercalados. Para compilar e executar Ir ao subdiretório src/jthread Salvar o arquivo em Main.java <dir>/jthread/ Lance o shell: executar cmd Posicione no diretório <dir> Compile: javac jthread/Main.java Execute: java -cp . jthread/Main Para executar somente Ir ao subdiretório dist Salve o arquivo .jar em <dir> Lance o shell: executar cmd Posicione no diretório <dir> Execute: java -jar JThreadContadoras.jar JAVARepositorio\JThreads\JThreadContadoras Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla 23 Criação de Threads em Java ◊Exemplo básico: threads contadoras class MinhaThread implements Runnable { public void run() { // a interface Runnable exige a implementação do método run String name = Thread.currentThread().getName(); System.out.println(name + " rodando"); for (int i=0;i < 1000; i++) { System.out.println(name + ": " + i); } System.out.println(name + " FIM ***"); } } public class Main { public static void main(String[] args) { System.out.println("INICIO DA THREAD MAIN ***"); Thread t1 = new Thread(new MinhaThread(), "thread 1"); Thread t2 = new Thread(new MinhaThread(), "\t\tthread 2"); t1.start(); t2.start(); } } Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla 24 Criação de Threads em Java ◊Exemplo básico: threads contadoras Quem faz o chaveamento de contexto? Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla 25 Criação de threads em Java ◊ Exercício 1: modifique o programa das threads contadoras de forma a criá-las estendendo a classe Thread class MinhaThread implements Runnable { .... ◊ Exercício 2: criar várias threads (> 4) e observar o chaveamento de contexto. Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla 26 Sumário 1d Yield, Sleep e Join 27 Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla Java THREADS: YIELD ◊ YIELD suspende a execução da thread atual e permite que outra ocupe o As flechas pontilhadas indicam swap. processador. O label das flechas indicam omotivo do swap: Swap: escalonador do SO; yield(): a chamada ao método causou a mudança. :Main new t1:MinhaThread new start() Início do run() t2:MinhaThread start() swap Início do run() swap yield() yield() Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla 28 Java THREADS: YIELD ◊ Exemplo de utilização de yield class MinhaThread implements Runnable { public void run() { // a interface Runnable exige a implementação do método run String name = Thread.currentThread().getName(); System.out.println(name + " rodando"); for (int i=0;i < 1000; i++) { System.out.println(name + ": " + i); // eh possivel passar o controle para outra thread implicitamente Thread.yield(); } System.out.println(name + " FIM ***"); } } public class Main { public static void main(String[] args) { System.out.println("INICIO DA THREAD MAIN ***"); Thread t1 = new Thread(new MinhaThread(), "thread 1"); Thread t2 = new Thread(new MinhaThread(), "\t\tthread 2"); t1.start(); t2.start(); } } 29 Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla Java THREADS: YIELD ◊ Exercício: no exemplo das threads contadoras, habilite o yield fazendo: java –jar JThreadContadoras.jar YIELD Programação Paralela e Distribuída - Fundamentos/UTFPR Prof. Cesar Augusto Tacla 30 Java Threads: SLEEP ◊ Sleep: suspende a execução da thread por um período Libera o processador para outras threads Faz a thread dormir por milisegundos ou nanosegundos Tempo de sleep não é preciso, depende do SO No exemplo das threads contadoras, cada thread dorme 2s a cada 3 contagens. Execute-o fazendo: java –jar JThreadContadoras.jar NO 2000 if (i%3==0) { try { thread.sleep(2000); // dorme 2 seg catch (InterruptedException e) {} } 31 Java THREADS: SLEEP ◊ SLEEP suspende a execução da thread atual por pelo menos x milisegundos e permite que outra ocupe o processador. :Main new t1:MinhaThread new start() t2:MinhaThread start() Início do run() swap swap Início do run() sleep(2000) Timed waiting swap 32 Java Threads: SLEEP ◊ Exercício [JAVA]: faça a simulação de um cruzamento controlado por dois semáforos utilizando apenas sleep para controlar os tempos de verde, amarelo e vermelho. Observe que o sleep fará sinaleiros não sincronizados. Para simplesmente executá-lo, ver próximo slide. 33 Sol. JAVARepositorio\JThreads\JCruzamento-v1 Java Threads: SLEEP ◊ Para executar o cruzamento fazer: ◊ Baixar o .jar de http://www.dainf.ct.utfpr.edu.br/~tacla/JAVARepositorio/JThreads/JCruzamento-v1/dist/ >java -jar JCruzamento.jar Estado SEM1:VERMELHO Estado SEM2:VERDE Estado SEM1:VERDE Estado SEM2:AMARELO Estado SEM2:VERMELHO Estado SEM1:AMARELO Estado SEM2:VERDE ... As linhas em destaque mostram que o semáforo SEM1 e o SEM2 ficaram verde ao mesmo tempo!!! 34 Java Threads: JOIN ◊ Thread.join(): permite que uma thread espere pelo término de duas ou mais threads :Main new t1:MinhaThread new start() join (t1, t2) t2:MinhaThread start() Início do run() swap waiting Fim join (t1, t2) swap Início do run() fim t1 fim t2 35 A thread main fica a espera de t1 e t2 Java Threads: JOIN public static void main (String[] args) { System.out.println("INICIO DA THREAD MAIN ***"); Thread t1 = new Thread(new MinhaThread(), "thread 1"); Thread t2 = new Thread(new MinhaThread(), "\t\tthread 2"); t1.start(); t2.start(); try { t1.join(); // a thread main aguarda o término de t1 t2.join(); // a thread main aguarda o término de t2 } catch (InterruptedException e) { System.out.println(“interrupcao"); } System.out.println("*** As duas threads encerraram a contagem ***"); // outras coisas da thread main } 36 Java Threads: JOIN ◊ JOIN: “une” as execuções das threads No exemplo das threads contadoras, para testar o JOIN, faça: java –jar JThreadContadoras.jar NO 2000 JOIN 37 Sumário 1e Prioridade entre threads 38 Prioridade entre threads ◊ Qual thread é escolhida para rodar? ◊ Depende... da implementação da JVM (Java Virtual Machine); da prioridade da thread da política/algoritmo de escalonamento do sistema operacional (ex. round-robin = rodízio por fatia de tempo = quantum) 39 Prioridade entre threads ◊ Exemplo: escalonamento no Linux [fonte http://www.inf.pucrs.br/~celso/SchedulerLinuxWindows.pdf] Cada processo possui uma prioridade, recalculada dinamicamente. O escalonador dá a CPU ao processo de maior prioridade O escalonador é preemptivo Para processos interativos (I/O bound) -> round-robin Para processos tempo-real -> FCFS (first come, first served) 40 Prioridade entre Threads ◊ Windows (2000/NT/XP) – ESCALONAMENTO [fonte http://www.inf.pucrs.br/~celso/SchedulerLinuxWindows.pdf] No Windows 2000/XP o escalonador utiliza múltiplas filas e as threads interativos (I/O bound) possuem prioridade sobre os CPU bound. O escalonamento é baseado em prioridades. Cada thread possui uma prioridade, que varia de 0 a 31 (0 é a menor e 31 a maior). A prioridade 0 é atribuída a uma thread especial, chamada zero thread, que é responsável por zerar as páginas livres no sistema. Somente esta thread pode receber a prioridade 0. As prioridades definem três classes de threads: Real time: prioridades de 16 a 31; Normal: prioridades de 0 a 15. Existe ainda uma classe especial chamada idle, a de mais baixa prioridade. Threads nesta classe somente executam quando não existem outras threads aptas (portanto, threads dessa classe não interferem na performance – não causam overhead). 41 Prioridade entre Threads ◊ Windows (2000/NT/XP) – ESCALONAMENTO O escalonador escolhe sempre a thread de maior prioridade. As threads da classe real time executam até terminar ou se bloquear (FCFS). As threads com prioridade normal (0 a 15) recebem fatias de tempo (RR). no Windows 2000 professional, a fatia de tempo é de 20 ms (para favorecer a interatividade). no Windows 2000 Server, a fatia é de 120 ms (para gerar menos trocas de contexto). Cada thread recebe uma prioridade base ao ser criada. Para os processos de tempo real (prioridade entre 16 e 31) esta prioridade não se altera. Além disso, uma thread tem uma prioridade inicial que indica sua prioridade relativa dentro do processo. Threads com prioridade entre 0 e 15 têm a prioridade ajustada em tempo de execução: Preemptadas por operações de I/0 recebem um bônus de aumento, que depende do periférico (ex. 1 para disco e 6 para teclado). Preemptadas por esgotar o quantum: prioridade reduzida. 42 Prioridade entre threads ◊ Mapeamento de threads Há de se considerar o mapeamento das threads da JVM para threads nativas do sistema operacional. Por exemplo, a JavaHotSpot VM na Solaris mapeia uma thread Java em uma thread nativa. Maiores detalhes em http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html ◊ As prioridades das Threads setadas no código Java são lógicas e dependem da implementação da JVM: public final void setPriority(int newPriority) public final int getPriority() MAX_PRIORITY = 10 constante inteira MIN_PRIORITY = 1 constante inteira NORM_PRIORITY = 5 prioridade atribuída por default 43 Prioridade entre threads ◊ Exercício: modificar a prioridade das threads contadoras e observar efeito na execução. Os dois últimos parâmetros modificam respectivamente as prioridades da thread 1 e 2. Recorda-se que a prioridade deve estar no intervalo [1, 10] No exemplo das threads contadoras, para testar o a prioridade, faça: java –jar JThreadContadoras.jar OFF 0 OFF 1 10 Prioridade thread 1 Prioridade thread 2 Código fonte e jar disponíveis em http://www.dainf.ct.utfpr.edu.br/~tacla/JAVARepositorio/JThreads/JThreadContadoras/ 44 Prioridade entre threads ◊ Exercício: considere um simulador de corridas de fórmula 1 que simula uma disputa entre dois pilotos: Schumacker e Barrichelo. Cada carro funciona de forma independente O tempo de cada volta é dado por um valor randômico. O programa deve esperar por este tempo sem fazer nada para então iniciar a próxima volta Ao final da corrida (quando os dois carros completam 5 voltas), o simulador mostra o tempo acumulado para cada um dos pilotos e aponta o vencedor ou empate. ◊ Responda Que comandos da linguagem Java você utilizaria para resolver cada um dos itens acima? JAVARepositorio\JThreads\JCarrosCorrida 45 Prioridade entre threads ◊ O código abaixo é uma solução do problema anterior. [JAVA] Altere o programa atribuindo maior prioridade a uma das threads (Schumacker ou Barrichelo). [TELEINFO] Execute o .jar for fun! Código fonte e .jar disponíveis em http://www.dainf.ct.utfpr.edu.br/~tacla/JAVARepositorio/JThreads/JCarrosCorrida/ * No exemplo seguinte, simulamos dois carros de corrida: Schumacker e Barrichelo. * Cada carro funciona de forma independente na sua thread e demora x milisegundos * para percorrer um determinado trecho da pista (x é um valor aleatório). * Para simular este tempo, utilizamos o método Sleep. Na thread Main aguardamos que * os dois carros terminem a prova para encerrar a corrida. Isto é feito através do join. * * * Rodar a partir da linha de comando * =================================== * 1. Salve este arquivo em <dir>/jcarroscorrida/ * 2. Lance o shell: executar -> cmd * 3. posicione no diretório <dir> * 4. compile: javac jcarroscorrida/Main.java * 5. execute: java -cp . jcarroscorrida/Main */ JAVARepositorio\JThreads\JCarrosCorrida 46 Sumário 1f Compartilhamento de memória 47 Compartilhamento de memória ◊ Compartilhamento de memória é um subtópico de comunicação inter-processos (IPC) fila de mensagens (SO) pipeline (SO) proc1 >> proc2 >> proc 3 área de memória compartilhada (SO) POSIX, threads envio de mensagens (rede) CORBA, JRMI, RPC, MPI ◊ Threads se comunicam por compartilhamento de memória Atributos (membro de classe e de instância) Objetos compartilhados 48 Exemplo de compartilhamento de memória class AreaMem { static String compartilhada= Thread.currentThread().getName();; } class Escritora extends Thread { public void run() { AreaMem.compartilhada = Thread.currentThread().getName(); } } public class Main { public static void main(String[] args) { Escritora E0 = new Escritora(); Escritora E1 = new Escritora(); E0.start(); E1.start(); System.out.println("Compartilhada = " + AreaMem.compartilhada); } } http://www.dainf.ct.utfpr.edu.br/~tacla/JAVARepositorio/JThreads/JCompartMemStaticSimples/ O atributo estático AreaMem.compartilhada é compartilhado entre as threads E0, E1 e main 49 Exercício ◊ Execute várias vezes o código do slide anterior ◊ Anote os valores impressos ◊ Qual a explicação? 50 Cenários de execução do exemplo Um dos cenários possíveis de execução; o valor impresso é “main” main:Thread c=“main” new E0:Thread E1:Thread start() start() print c run() X run() c=“Thread-0” c=“Thread-1” X X 51 Exemplo por atributo estático public class Main extends Thread { static int c = 0; // compartilhado private int ini, fim; // nao compartilhados public Main(int ini, int fim) { this.ini = ini; this.fim = fim; } public void run() { System.out.println(Thread.currentThread().getName() + " while (c >= ini && c <= fim) { System.out.println(Thread.currentThread().getName() c = (int)(Math.random()*10); System.out.println(Thread.currentThread().getName() } System.out.println(Thread.currentThread().getName() + " } !!! wait "); + " antes " + c); + " depois " + c); !!! fim "); public static void main(String[] args) { new Main(0, 4).start(); new Main(5, 10).start(); } } JAVARepositorio\JThreads\JCompartMemStatic 52 Cenário de execução do exemplo main:Main <<create>> Thread-0:Main Thread-1:Main start() start() swap run() 0≤c≤4 c:=2 0≤c≤4 c:=5 X swap X 5 ≤ c ≤ 10 c:=6 5 ≤ c ≤ 10 c:=2 X O atributo estático c é compartilhado pelas threadas 53 Exercício Explique porque a seguinte sequência de mensagens não é possível main:Main <<create>> Thread-0:Main Thread-1:Main start() start() swap run() 0≤c≤4 c:=2 X swap run() 5 ≤ c ≤ 10 swap 0≤c≤4 c:=5 swap X 5 ≤ c ≤ 10 c:=6 5 ≤ c ≤ 10 c:=2 X 54 Exercício Objetivo: entender o compartilhamento de memória por atributos estáticos ◊ Para a classe abaixo class Schedule static int x = 0; static int y = 0; public static int op1() { x = 1; return y; } public static int op2() { y = 2; return 3*x; } Se uma thread chama op1 e outra, op2, que resultados podem ser retornados pelos métodos op1 e op2? (Vijay K. Garg, 2004, p. 15) 55 Exemplo por objeto compartilhado class Conta { int saldo = 0; } class Retirada extends Thread { private Conta c; public Retirada(Conta c) { this.c = c; } public void run() { int vlrs[] = {10, 20, 30, 40, 50, 60}; for (int i=0; i <vlrs.length; i++) c.saldo -= vlrs[i]; } } Objeto compartilhado class Deposito extends Thread { private Conta c; public Deposito(Conta c) { this.c = c; } public void run() { int vlrs[] = {40, 50, 60, 10, 20, 30}; for (int i=0; i <vlrs.length; i++) c.saldo += vlrs[i]; } } public class Main { public static void main(String[] args) { Conta c = new Conta(); Thread d = new Deposito(c); Thread r = new Retirada(c); d.start(); r.start(); try { d.join(); r.join(); } catch (InterruptedException e) { } System.out.println("Saldo=" + c.saldo); } } http://www.dainf.ct.utfpr.edu.br/~tacla/JAVARepositorio/JThreads/JCompartMemObjetoCC/ 56 Exercício ◊ Faça uma classe Java que faça busca paralela num array de inteiros. A classe deve implementar o método: public static int busca(int x, int[] A, int nThreads) O método cria nThreads Estas threads devem compartilhar o objeto A. Cada thread busca pelo inteiro x em uma parte de A, por exemplo, da posição 0 até a 10, outra da 11 a 20, ... Se uma das threads encontra x, então retorna-se o índice i, tal que A[i] = x Caso contrário, o método retorna -1 Quando uma das threads encontra o número x, as outras devem parar a busca! sol. JAVARepositorio\JThreads\JBuscaParalela 57 Sumário 1g Máquina de estados de uma thread 58 Estados de uma thread As transições observadas até o momento estão em destaque Object.wait(timeout) Thread.join(timeout) Thread.sleep(delay) TIMED WAITING Object.notify() Object.notifyAll() join thread termina fim do sleep Obteu lock new() NEW start() Escolhida pelo scheduler Thread.yield() swap do scheduler RUNNING Object.wait() Thread.join() BLOCKED RUNNABLE join threads terminam Object.notify() Object.notifyAll() WAITING TERMINATED Fim do método run() Synchronized(obj) 59 http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Thread.State.html Estado de uma thread: detalhe do runnable RUNNING Escolhida pelo scheduler Em runnable, a thread pode ser escolhida pelo scheduler para entrar em execução. Uma vez em execução, pode liberar o processador por yield ou por swap do scheduler. Thread.yield() swap do scheduler RUNNABLE 60 Sumário 2 PROBLEMAS DE CONCORRÊNCIA 61 Sumário 2a Problemas de Concorrência 62 Problemas de concorrência ◊ Processos compartilham dados ◊ Sincronizar acesso aos dados é necessário ◊ Exemplo x é uma variável compartilhada (inicialmente zero) Thread T0 faz x := x + 1 Thread T1 faz x := x + 1 x deve ser 2 ao final ◊ Porém, se x := x + 1 não for uma operação atômica... 63 RACE CONDITION: atualização perdida ◊ x = x + 1 em código de máquina (inicial x=0) LD R, x load registrador R com x INC R incrementar R ST R, x store R em x ◊ A execução de T0 e T1 é intercalada P0: LD R, x R=0 P0: INC R R =1 0 10 10 Variável x P1: LD R, x R = 0 P0: ST R, x Registrador R P1: INC R R =1 P1: ST R, x x =1 10 1 x =1 Resultado final: x = 1 problema da atualização perdida 64 Problemas de concorrência ◊ x := x + 1 deve ser executada atomicamente ◊ Região/Seção crítica necessida atomicidade ◊ Para garantir atomicidade, exclusão mútua processos querem o recurso, mas só um pode utilizá-lo, caso contrário o recurso pode ficar num estado inconsistente Recurso compartilhado Processo 2 Processo 1 Quem ganha a disputa? 65 RACE CONDITION: atualização perdida CC = Conta corrente Saldo: R$ 100,00 Transação T1: crédito de R$5,00 Transação T2: crédito de 10% de juros Situação desejada: 1) Aplica-se o juros 2) credita-se R$ 5,00 Funcionamento das transações 1) 2) 3) s:=CC.getSaldo() Crédito na variável s CC.setSaldo(s) Saldo da conta deve ser: (R$100,00 * 1,1) + 5,00 = R$ 115,00 Problemas: Leitura suja Atualização perdida 66 RACE CONDITION: atualização perdida Quando uma transação sobrescreve valores produzidos por outra TRANSAÇÃO T2 Juros de 10% TRANSAÇÃO T1 Creditar R$ 5,00 S:= CC.getSaldo() S:= S + 5 // R$100 // S = 105 S = CC.getSaldo( ); // R$100 S = S * 1,10; // R$ 110,00 CC.setSaldo(S); // R$ 110,00 CC.setSaldo(S) // R$105 Sobrescreve o valor produzido por T2 Saldo final = R$ 105,00 67 RACE CONDITION: leitura suja Quando uma transação lê valores intermediários de outro transação - atributos que estão sendo modificados em outra transação Saldo A = R$ 200,00 Saldo B = R$ 300,00 TOTAL = R$ 500,00 Transfere R$100,00 da Conta A para a conta B TRANSAÇÃO T1 A.retirar(100) Após transferência Saldo A = R$ 100,00 Saldo B = R$ 400,00 TOTAL = R$ 500,00 Relatório com o saldo total das agências TRANSAÇÃO T2 total = A.saldo( ); // R$100 total = total + B.saldo( ); // R$300 Mostrar total = R$ 400,00 B.depositar(100) 68 CONCORRÊNCIA ◊ Exemplo de leitura suja/atualização perdida: executar o código abaixo e observar que o número apontado pelo contador central difere da soma dos contadores locais devido aos problemas de leitura suja e atualização perdida. Códigos fonte e .jar disponíveis em http://www.dainf.ct.utfpr.edu.br/~tacla/JAVARepositorio/JThreads/JRaceConditionRoletas/ Contador central 160.000.000 Atualizado pelas duas roletas de forma concorrente 40.000.000 120.000.000 Roleta e contador local 69 Sumário 2b Vivacidade: deadlock, livelock e starvation 70 Vivacidade ◊ VIVACIDADE: um pedido de uma thread para entrar/acessar uma seção crítica será atendido mais cedo ou mais tarde DEADLOCK: uma thread t1 bloqueia a espera de uma seção crítica ocupada por t2 que, por sua vez, está bloqueada a espera da seção crítica ocupada por t1 grafo wait-for cíclico STARVATION: quando uma thread solicita entrada numa seção crítica e nunca é atendida (ex. porque as outras tem maior prioridade) LIVELOCK: duas threads não conseguem avançar porque uma muda seu estado em resposta à mudança de estado da outra (ex. duas pessoas no corredor que sempre escolhem o mesmo lado para desviarem). 71 Exemplo de deadlock ◊ DEADLOCK: filósofos podem bloquear-se. ? ? Applet demo disponível em http://www.doc.ic.ac.uk/~jnm/concurrency/classes/Diners/Diners.html / 72 Exemplo de starvation ◊ STARVATION: um dos filósofos nunca come porque o outro tem maior prioridade. Prioridade por ser o mais sábio 73 Exemplo de livelock ◊ LIVELOCK: filósofos podem bloquear-se porque mudam de estado continuament em função do outro. 74 Problema dos filósofos 1. 2. Só um dos filósofos decide comer Os dois decidem comer ao mesmo tempo, cada um pega um pauzinho P1 c/ F1 Filósofo 1 termina de comer F1 F1 pega decide 2 pauzinhos comer P2 c/ F1 P1 c/ F21 Pauzinho 1 F1 e F2 pensam Pauzinho 2 F2 decide comer F2 pega 2 pauzinhos Filósofo 2 termina de comer P2 c/ F2 75 Problema dos filósofos ◊ Solução para o problema dos filósofos: coordenar as ações Um dos filósofos devem solicitar os recursos na ordem inversa dos outros Ex. todos pegam o objeto da esquerda e depois o da direita. Facilmente visível com dois filósofos 76 Problema dos filósofos ◊ SOLUÇÃO: coordenar as ações. Inverter a ordem. WAIT 77 Solução para filósofos Primeiro o p1 depois o p2 Se os dois decidem comer ao mesmo tempo, somente um deles conseguirá pegar o pauzinho 1 P1 c/ F1 P1 c/ F21 Pauzinho 1 Filósofo 2 termina de comer Filósofo 1 termina de comer P1 e P2 C/ F1 F1 decide comer F1 e F2 pensam F2 decide comer P1 E P2 c/ F2 Pauzinho 2 78 Problema dos filósofos ◊ Exercício Verificar se a solução apresentada funciona para 4 filósofos sendo o f2 aquele que pega na ordem invertida. Represente em um diagrama de seqüência a situação na qual todos tentam pegar os pauzinhos ao mesmo tempo. f1 p1 p4 f2 f4 p3 p2 f3 79 Sumário 2c Soluções aos problemas de concorrência 80 SOLUÇÕES PARA CONCORRÊNCIA Como resolver os problemas de acesso à seção crítica? ◊ Exclusão mútua por hardware Desabilitar interrupções instruções de máquina atômicas de mais alto-nível ◊ Primitivas de sincronização Semáforos Monitores monitor 81 MUTEX por HARDWARE ◊ Que soluções existem em hardware para o problema de exclusão mútua (MUTEX)? Desabilitar interrupções: antes de entrar na SC, o processo desabilita as interruções Em máquinas monoprocessador pode funcionar com alguns incovenientes... interrupções do clock (time-slice) ficam desabilitadas mas, em máquinas multiprocessadores? É possível desabilitar interrupções em todos os processadores? Instruções atômicas mais abstratas fornecidas pelo hardware Test and set Swap 82 SEMÁFOROS ◊ SEMÁFORO (Dijkstra): é uma primitiva de sincronização de acesso à seção crítica que soluciona o problema de busywait. ◊ API do sistema operacional (SO) Disponível na API do S.O. Java não oferece semáforos ◊ Dois tipos de semáforos Binário contador 83 SEMÁFORO BINÁRIO ◊ Semáforo binário: Valor inicial: verdadeiro Fila de processos em wait: vazia ◊ Operações básicas P() wait V() signal (notify) 84 Semáforo binário T1:thread S:SemaforoBinario T2:thread true P( ) false P( ) Na seção crítica V( ) true false wait notify( ) Na seção crítica V( ) true 85 SEMÁFOROS ◊ Semáforo contador: Valor inicial: valor inteiro = número de processos permitidos na SC ◊ Operações básicas P() wait decrementa um contador V() signal (notify) incrementa contador Ver código 86 Semáforo contador T1:thread S:SemaforoContador T2:thread T3:thread 2 P( ) 1 P( ) 0 Na seção crítica P( ) V( ) 1 wait notify( ) 0 V( ) 1 Há dois recursos, neste caso o semáforo é inicializado com 2 87 PRODUTOR-CONSUMIDOR ◊ Problema do Produtor-Consumidor Solução utilizando semáforos K=5 PRODUTOR CONSUMIDOR BUFFER COMPARTILHADO 88 PRODUTOR-CONSUMIDOR ◊ PRODUTOR-CONSUMIDOR Solução utilizando semáforos entrada 4 3 2 5 1 ... saída TAM -1 0 89 PRODUTOR-CONSUMIDOR ◊ Problemas Exclusão mútua no acesso ao buffer (ou o produtor ou o consumidor) Consumidor não deve buscar itens quando o buffer está vazio Produtor não pode produzir se buffer estiver cheio SINCRONIZAÇÃO CONDICIONAL: Um processo aguarda que uma condição se torne verdadeira antes de continuar suas operações. Exemplos: Consumidor aguarda a existência de um item Produtor aguarda espaço no buffer para produzir 90 PRODUTOR-CONSUMIDOR ◊ Exercício (solução utilizando semáforos) Baixe o código fonte Faça o diagrama de sequência para o cenário seguinte: O produtor produz um item (double) O consumidor consome. O consumidor tenta consumir um item que não existe. Código fonte e .jar disponível em JAVARepositorio/JThreads/JProdutorConsumidorSemaforos 91 SEMÁFOROS ◊ Outros problemas bem-conhecidos Leitor-escritor em banco de dados Não acessam o BD concorrentemente Leitor e escritor Dois escritores Múltiplos leitores acessam concorrentemente Filósofos 92 MONITOR LOCK ◊ MONITOR: mais alto nível e mais fácil de utilizar que semáforo ◊ Java só trabalha com monitores ◊ Todo objeto em Java é um monitor 93 MONITOR LOCK ◊ Quando uma thread executa um bloco de código sincronizado, ela fecha o acesso às demais. Outra thread que tentar acessar a seção crítica (o bloco de código) não conseguirá até que a primeira libere o acesso. ◊ Em java, o bloqueio é feito no objeto e não no método. ◊ Isto é útil quando duas ou mais threads devem atualizar a mesma área de mémória (atributo compartilhado). 94 EXEMPLO MONITOR LOCK T1:thread obj:Compartilhado T2:thread Lock livre método sincronizado( ) Lock ocupado Na seção crítica método sincronizado( ) fim método waiting notify( ) Na seção crítica fim método Lock livre 95 Exemplo em JAVA ◊ Problema das roletas e contador central /** * Contador é um objeto compartilhado pelas threads roleta 1 e roleta 2. */ class ContadorCentral { protected int numPessoas=0; /** * O código que faz a atualização da variável numPessoas é uma seção crítica. * Somente um processo (ou thread) pode executá-lo por vez. Para impedir que * mais de uma thread atualize numPessoas, utiliza-se a palavra-chave * synchronized. **/ protected synchronized void somarNumPessoas(int n) throws InterruptedException { numPessoas += n; } } Ao executar somarNumPessoas de uma certa instância de ContadorCentral o lock fica retido. Qualquer outro método (se houvesse) da instância não poderia ser executado enquanto o somarNumPessoas não terminasse (lock liberado). JAVARepositorio\JThreads\JRaceConditionRoletasSol 96 Exemplo em JAVA: outra forma ◊ Problema das roletas e contador central (equivale ao anterior, só muda a sintaxe) /** * Contador é um objeto compartilhado pelas threads roleta 1 e roleta 2. */ class ContadorCentral { protected int numPessoas=0; /** * O código que faz a atualização da variável numPessoas é uma seção crítica. * Somente um processo (ou thread) pode executá-lo por vez. Para impedir que * mais de uma thread atualize numPessoas, utiliza-se a palavra-chave * synchronized. **/ protected void somarNumPessoas(int n) throws InterruptedException { synchronized (this) { numPessoas += n; } } } JAVARepositorio\JThreads\JRaceConditionRoletasSol 97 SINTAXE PARA SYNCHRONIZED São construções equivalentes public synchronized void metodo() { …. …. …. } public void metodo() { synchronized (this) { …. …. …. } } Métodos estáticos podem ser sincronizados – equivale a um lock de classe 98 MONITOR LOCK: SEMÂNTICA A thread deve obter o lock deste objeto para entrar na SC ◊ Semântica synchronized (object) { como uma seção crítica …. …. …. Lock é para objeto (não para o bloco de código ou método) enterMonitor(): se obtém o lock entra no monitor se não aguarda; exitMonitor(): libera o lock e notifica processos bloqueados } 99 EXERCÍCIO ◊ Baixar o programa JRaceConditionRoletas do repositório e corrigir o problema de condição de corrida na atualização do contador central utilizando bloco de código synchronized. Código fonte disponível em JAVARepositorio/JThreads/JRaceConditionRoletas/ ◊ Idem para o programa que atualiza saldo da conta corrente Código fonte disponível em JAVARepositorio/JThreads/JCompartMemObjetoCC/ FIM Solução em RepositorioJAVA/JThreads/JRaceConditionRoletasSol 100 MONITOR LOCK: WAIT/NOTIFY ◊ Há situações onde, duas threads devem executar de forma concorrente dois blocos de código sincronizados do mesmo objeto. ◊ Mas foi dito que uma vez que uma thread pega o lock de um objeto ela só o libera ao final do método!!! ◊ Foi uma meia verdade... ;-) 101 MONITOR LOCK: WAIT/NOTIFY THREAD 1 tira põe THREAD 2 WAIT pedestal ≠ vazio Tira peça Notifica que tirou WAIT pedestal vazio Põe peça Notifica que colocou Pedestal Objeto compartilhado 102 MONITOR LOCK: WAIT/NOTIFY class Pedestal { boolean livre = true; } class RoboProdutor implements Runnable { Pedestal p; RoboProdutor(Pedestal p) { this.p = p; } public void run() { while (true) { synchronized (p) { while (!p.livre) { p.wait(); } println("Peça colocada"); p.livre=false; p.notifyAll(); } } } } class RoboConsumidor implements Runnable { Pedestal p; RoboConsumidor(Pedestal p) { this.p = p; } public void run() { while (true) { synchronized (p) { while (p.livre) { p.wait(); } println("Peça retirada"); p.livre=true; p.notifyAll(); } } } } Catch e try foram omitidos Wait libera o lock; Notify não libera o lock JAVARepositorio\JThreads\JExemploWaitNotifyPedestal 103 EXEMPLO MONITOR LOCK Produtor:Runnable p:Pedestal Consumidor:Runnable Lock Início do run() synchronized(p) Obtém o lock de p Início do run() Lock synchronized(p) “Peça Colocada” p.livre = false Livre=false notifyAll( ) waiting pelo lock fim bloco sync: libera o lock synchronized(p) Obtém o lock de p wait p.livre Lock Lock Lock Obtém o lock p.livre = true Livre=true “Peça Retirada” 104 EXERCÍCIO ◊ Baixe o código fonte dos robôs e pedestal ◊ Inclua mais um robô consumidor ◊ Identifique na mensagem impressa qual robô consumidor que faz a retirada da peça ◊ Verifique se o programa funciona corretamente. http://www.dainf.ct.utfpr.edu.br/~tacla/JAVARepositorio/JThreads/JExemploWaitNotifyPedestal/ 105 MONITOR LOCK: WAIT, NOTIFY ◊ Cada monitor tem uma fila de processos bloqueados ◊ 3 métodos especiais podem ser usados dentro do monitor synchronized (object) { …. object.wait(); …. object.notify(); …. object.notifyAll( ); …. } Thread Libera o lock do object e passa ao estado waiting Se a fila não estiver vazia, pega um processo qualquer da fila e o desbloqueia Se a fila não estiver vazia, desbloqueia todos os processos da fila que vão disputar o lock – mas só um será escolhido pelo escalonador (ineficiente se houver muitas threads) 106 NOTIFY x NOTIFY ALL ◊ Utilizar notify no lugar de notify all somente quando as duas condições abaixo forem verdadeiras: 1. Threads em waiting possuem mesma condição de espera (ex. estahVazio). Todas as threads executam a mesma lógica quando saem do wait. 2. One-in, one-out. A notificação sobre a variável utilizada na condição habilita somente uma thread a prosseguir. Fonte: Goetz, B. Java Concurrency in Practice, ed. Pearson Education, 2006, seção 14.2.4 107 Exemplo típico synchronized(obj) { while(<condição>) { try { wait(); }catch (InterruptedException e) { } } // alguma coisa notifyAll(); } 108 Métodosde bloco de código sincronizado Usar somente em blocos de código sincronizado (caso contrário, erro de execução IllegalMonitorStateException) ◊ Object.wait( ) libera o lock ◊ Object.wait(<t em ms>) libera o lock ◊ Object.notify() ◊ Object.notifyAll() ◊ Importante: não manipulam lock Thread.yield() não libera o lock Thread.sleep(<t em ms>) não libera o lock Thread.suspend() não utilizar – deprecated Thread.resume() não utilizar - deprecated 109 Tipos de monitores ◊ Há duas implementações clássicas de monitores Hoare: o processo que faz a notificação é interrompido Estilo Java: o processo que faz a notificação não é necessariamente interrompido 110 Dois tipos de monitores Processo 0 synchronized (object) { …. object.wait(); Processo 1 synchronized (object) { …. object.notify(); Qual processo continua a execução nesse ponto? …. …. } } Somente um processo pode estar no monitor: 2 possibilidades 111 TIPO 1: MONITOR DE HOARE Process 0 synchronized (object) { …. object.wait(); Process 1 synchronized (object) { …. object.notify(); …. } …. } Processo 0 assume a execução assim que o 1 faz notify 112 Tipo 1: MONITOR DE HOARE Process 0 synchronized (object) { if (x != 1) object.wait(); A condição de sincronização (x=1) é seguramente verdadeira quando o processo é notificado Process 1 synchronized (object) { x=1; O processo que notifica object.notify(); é interrompido assert(x==1); // x é igual a 1 x++; } // x pode ser ≠ 0 neste ponto } No monitor HOARE, é possível fazer condição sincronizada com if 113 TIPO 2: MONITOR ESTILO JAVA Process 0 synchronized (object) { …. object.wait(); Process 1 synchronized (object) { …. object.notify(); // código // código } …. } No monitor estilo Java, o Processo 1 continua a executar após o notify 114 Tipo 2: MONITOR JAVA Process 0 synchronized (object) { if (x != 1) object.wait(); P0 realiza operações assumindo x=1, sendo que x pode ter outro valor Process 1 synchronized (object) { x=1; object.notify(); assert(x == 1); // x must be 1 x++; } assert(x==1); // falso x++; } Processo 1 continua a executar e pode modificar o valor de x. A sincronização condicional com um simples if pode não funcionar. 115 Tipo 2: MONITOR JAVA Process 0 synchronized (object) { while (x != 1) Só sai do while object.wait(); quando x=1 P0 realiza operações assumindo x=1, sendo que x será igual a 1 Process 1 synchronized (object) { x=1; object.notify(); assert(x == 1); // x must be 1 x++ } assert(x==1); // verdadeiro x++; } Para solucionar o problema do slide anterior, é preciso colocar um while (também resolve o caso de interrupções espúrias). 116 Dois tipos de monitores ◊ O monitor Java é mais usual Porém, é prudente verificar o tipo de monitor quando for utilizar outra linguagem ◊ Código de sincronização é extremamente difícil de debugar Muitos bugs ficam escondidos durante anos sem que ninguém perceba Faça certo da primeira vez! 117 Resumo ◊ Por que necessitamos de primitivas de sincronização? Busy waiting desperdiça ciclos de CPU Primitivas de sincronização necessitam suporte do SO ◊ Semáforo: Binário, contador Produtor-consumidor ◊ Monitor: Mais fácil e mais popular que semáforos Dois tipos: Hoare e estilo Java 118 EXERCÍCIO ◊ Problema do barbeiro dorminhoco Um barbeiro corta o cabelo de qualquer cliente. Se não há clientes, o barbeiro tira uma soneca. Há várias threads, uma para cada cliente. Um cliente aguarda pelo barbeiro se há ao menos uma cadeira vazia na barbearia, Caso contrário, o cliente sai da barbearia imediatamente. Se há uma cadeira disponível, então o cliente senta. Se o barbeiro está dormindo, então o cliente acorda-o. Existem <n> cadeiras na barbearia. Faça um programa para a classe BarbeiroDorminhoco utilizando monitor 119