Prévia do material em texto
CURSO DE CIÊNCIA DA COMPUTAÇÃO DISCIPLINA: DESENVOLVIMENTO DE APLICAÇÕES DISTRIBUÍDAS TEMA 03 – PROGRAMAÇÃO CONCORRENTE TEXTO PARA APOIO AO ESTUDO 1) Contextualização No passado, os computadores tinham, normalmente, apenas um processador e as tarefas alternavam o uso do processador, de modo que elas pudessem ser executadas ao longo do tempo, conforme ilustra a Figura 1. Ou seja, mesmo com apenas um processador, era possível editar um texto, enviar um arquivo para a impressora, etc. Figura 1 – Ilustração de tarefas sendo executadas em um único processador. Atualmente, os computadores têm, normalmente, vários processadores, de modo que as tarefas possam ser executadas simultaneamente, conforme ilustra a Figura 2. Figura 2 – Ilustração de tarefas sendo executadas em vários processadores. E as Threads podem ser definidas como sendo subdivisões de processos, ou seja, subdivisões do programa e suas linhas de instruções. Os programas implementados com Java podem ter várias Threads (Multithreading), ou seja, atividades concorrentes que permitem melhorar o desempenho dos programas. Java oferece várias funcionalidades para trabalharmos com as Threads, objetivando otimizar a execução. Computacionalmente, concorrência se refere a tarefas independentes que executam simultaneamente em um computador. Mas, quando se usa a palavra simultaneamente, é importante fazer uma observação porque a simultaneidade pode ser real, se o computador tiver mais de um processador ou um processador com vários núcleos, e a simultaneidade também pode ser aparente se o computador tiver apenas um processador de apenas um núcleo. Sistemas Operacionais, já há algum tempo, permitem a execução de tarefas concorrentes como, por exemplo, editar um texto, ouvir música, acessar uma página web, etc. Neste caso, trata-se de concorrência ao nível de processos. Porém, dentro de um processo, pode-se ter várias tarefas simultâneas. Tarefas concorrentes podem rodar dentro de um processo denominado thread. Outro conceito relacionado à concorrência é o paralelismo e há diferentes definições com o conceito de concorrência. Há autores que se referem à concorrência quando as aplicações são executadas por meio de várias threads e um único núcleo de processador, ou seja, simultaneidade aparente. Estes autores, normalmente, consideram que o paralelismo se refere às aplicações com múltiplas threads são executadas em processadores com vários núcleos ou em um computador com mais de um processador, ou seja, simultaneidade real. Outros autores, porém, se referem à concorrência quando as aplicações são executadas sem ordem pré-definida e eles se referem ao paralelismo quando as threads são executadas de maneira ordenada. Normalmente, os Sistemas Operacionais consideram a distinção entre processos pesados e leves: • Processos pesados: são, supostamente, completamente separados uns dos outros; • Processos leves (threads): compartilham espaços de memória, entre outros recursos. Os Sistemas Operacionais lidam com a alocação de recursos necessários (como memória, tempo de processamento, entrada/saída) para que os processos sejam executados, garantindo que não haja interferência entre eles e essa característica é denominada de isolamento. A Figura 3 ilustra o princípio de isolamento entre os processos. Dois programas (P1 e P2) têm classes distintas (A e B), mas cada uma delas tem uma variável com o mesmo nome (var). Quando as classes estão em execução, as regiões de memória alocadas para as variáveis de mesmo nome estão isoladas. Figura 3 – Ilustração do princípio de isolamento. Por outro lado, quando utilizamos threads, podemos ter problemas tendo em vista que há o compartilhamento de recursos. Para exemplificar, vamos considerar, por hipótese, que temos um programa que inicia uma variável com o valor 5. Depois, atribui-se o valor 1 a esta variável e, depois, o valor 8: int var = 5; var = 1; var = 8 Se as atribuições de valores fossem feitas por threads diferentes, elas acessariam o mesmo espaço de memória destinado à varável e, desse modo, qual seria o valor final da variável, se não forem observadas as questões de sincronização? Figura 4 – Ilustração da importância da sincronização das threads. Para que não haja problema, a princípio, a thread associada a atribuição do valor 1 deve ser executada e, só após a finalização desta execução, a thread associada a atribuição do valor 8 será executada, conforme ilustra a Figura 5. Figura 5 – Sequência de execução das threads. Portanto, uma thread pode ser definida como um trecho de código que tem a característica de poder ser executado juntamente com outros processos. Especialmente quando há o compartilhamento de memória e outros recursos, as threads não executam ao mesmo tempo em um processador e possibilitam que outras threads também sejam executadas. A Figura 6 ilustra a execução e latência de quatro threads: Figura 6 – Ilustração dos tempos de execução e latência de threads. Portanto, como ilustrado na Figura 6, é importante pensar na sincronização das threads, controlando a ordem de execução, de modo que o resultado do processamento seja efetivo. São dois tipos de sincronização: • Sincronização por cooperação: neste caso, uma thread espera a finalização da execução de outra thread para dar continuidade na sua execução. • Sincronização por competição: como o próprio nome indica, as threads competem pela utilização dos recursos que não podem ser utilizados simultaneamente por diferentes threads. Para que a sincronização possa ser efetiva, é necessário utilizar algum recurso que permita atrasar a execução das threads que precisam esperar pela execução de outras threads. Este recurso é o escalonador. 2) Problemas em computação concorrente A programação concorrente envolve certas particularidades e, consequentemente, deve-se ter alguns cuidados ao implementar um programa que envolva esta abordagem. Alguns problemas típicos em programação concorrente são os seguintes: • Lockout (Trancamento): este tipo de problema ocorre quando uma tarefa aguarda por um evento que nunca ocorrerá e, consequentemente, a tarefa não será executada; • Deadlock (Impasse): neste caso, o problema ocorre pelo fato de existir uma tarefa bloqueada, esperando por algum evento de outra thread que também está bloqueada; • Starvation (Inanição): ocorre quando uma tarefa aguarda por um evento que foi postergado indefinidamente; • Indeterminismo: este problema se refere a várias execuções de uma tarefa que podem gerar resultados diferentes.