Escolar Documentos
Profissional Documentos
Cultura Documentos
EXEMPLO:
Uma escola está fazendo sua matrícula apenas pela internet, o número de vagas é 5, dois
usuários estão fazendo a matrícula no exato momento, finalizam a matrícula. A operação que
o programa usa da região crítica: matrícula finalizada -1.
Se os dois usuários fazem a operação ao mesmo tempo, quando a matricula for finalizada
subtrai-se 1 vaga:
Quando um terceiro usuário for fazer esta mesma matrícula, o número de vagas será expresso
como 4, sendo que na verdade deveria ser 3. Isto causará instabilidade e poderá comprometer
todo o sistema. A solução para este tipo de caso é a certeza de exclusão mútua, isto é, apenas
um processo pode acessar a região crítica por vez; Os mecanismos que implementam
a exclusão mútua utilizam um protocolo de acesso à região crítica. Toda vez que um processo
for executar sua região crítica, ele é obrigado a passar por um controle de entrada e outro de
saída.
1
EXCLUSÃO MÚTUA COM ESPERA ATIVA
É uma técnica em que um processo verifica uma condição repetidamente (são feitos testes
contínuos em uma variável) até que ela seja verdadeira (true), como esperar o pressionamento
da tecla de um teclado ou a espera da liberação de recurso em exclusão mútua. Também pode
ser usada para gerar atrasos na execução do sistema por um período determinado de tempo. Ela
pode ser uma estratégia válida em certas circunstâncias, mas no geral é considerada um anti
padrão e deve ser evitada, já que o tempo de CPU gasto poderia ser usado noutra tarefa.
Desativando as Interrupções
A mudança de contexto de execução de processos só pode ser realizada com o uso de
interrupções. A forma mais simples de garantir a exclusão mútua é fazer com que o processo
desabilite as interrupções ao entrar na região crítica, e antes de sair as habilite novamente. Com
isso a CPU não fará um chaveamento no momento em que o processo estiver na região crítica,
pois o chaveamento vem de uma interrupção através de um relógio.
- O processo pode não tornar a habilitar suas interrupções ao sair da região crítica.
- O processo pode entrar em loop dentro de sua região crítica, e nenhum outro evento o
interromperá.
Variáveis de Bloqueio
Quando uma variável "lock" estiver como 0, significa que a região crítica esta livre, e 1 esta
ocupada. Assim, antes de entrar cada processo testa o valor da variável "lock", se for 0, coloca
como 1 e entra na região crítica, após sair coloca o valor 0, se o valor já for 1, aguarda até ser
0.
2
Alternância Estrita
Neste método, é criada uma variável "turn", com valor inicial 0, a imagem abaixo mostra dois processos 'a'
e 'b' utilizando este método.
Como "turn" esta como 0, o processo 'a' não fica "preso" no while, e assim executa a região crítica, após
terminar, ele seta "turn" para 1 e parte para o resto do código, caso ocorra um chaveamento e
o processo 'b' tente executar a região crítica antes que o processo 'a' sete "turn" como 1, ele
ficara em um loop, apenas testando a variável "turn"(espera ativa).
Deadlock
A definição textual de deadlock por ser muito abstrata, é mais difícil de se compreender do que
a representação por grafos, que será resumida mais adiante. No entanto, algumas observações
são pertinentes:
3
❖ O deadlock pode ocorrer mesmo que haja somente um processo no SO, considerando
que este processo utilize múltiplos threads e que tais threads requisitem os recursos
alocados a outros threads no mesmo processo;
❖ O deadlock independe da quantidade de recursos disponíveis no sistema;
❖ Normalmente o deadlock ocorre com recursos, tais como dispositivos, arquivos,
memória. Apesar de a CPU também ser um recurso para o SO, em geral é um recurso
facilmente preemptível, pois existem os escalonadores para compartilhar
o processador entre os diversos processos, quando trata-se de um
ambiente multitarefa.
Erros de deadlock podem ocorrer em Bancos De Dados. Suponha que uma empresa tenha
vários vendedores e vários pontos de venda ou caixas. O vendedor A vendeu 1 martelo e 1
furadeira. O sistema, então, solicita o travamento do registro da tabela "ESTOQUE", que
contém o total de martelos em estoque e, em seguida, solicita o travamento do registro que
contém o total de furadeiras em estoque. De posse da exclusividade de acesso aos dois registros,
ele lê a quantidade de martelos, subtrai 1 e escreve de novo no registro; o mesmo ocorre com
relação ao registro de furadeiras. Observe, no entanto, que existem diversos caixas operando
simultaneamente, de forma que, se algum outro caixa, naquele exato instante, estiver vendendo
uma furadeira, ele ficará aguardando a liberação do registro das furadeiras para depois alterá-
lo. Note que ele só altera os registros depois que for dada exclusividade para ele de TODOS os
recursos que ele precisa, ou seja, de todos os registros. Suponha agora que, em outro caixa,
foram vendidos 1 furadeira e 1 martelo e que o outro caixa solicitou o travamento do registro
com a quantidade de furadeiras e agora quer o acesso ao de martelos; no entanto o registro de
martelos está travado para o primeiro caixa. Nenhum deles devolve o recurso (registro) sobre
o qual tem exclusividade e também não consegue acesso ao outro registro que falta para
terminar a operação. Isto é um deadlock.
4
CONDIÇÕES NECESSÁRIAS PARA A OCORRÊNCIA DE DEADLOCK
No texto acima, foi dito que o deadlock ocorre naturalmente em alguns sistemas. No entanto,
é necessário ressaltar que tais sistemas precisam obedecer a algumas condições para que uma
situação de deadlock se manifeste.
Essas condições estão listadas abaixo. As três primeiras caracterizam um modelo de sistema, e
a última é o deadlock propriamente dito. Processos que estejam de posse de recursos obtidos
anteriormente podem solicitar novos recursos. Caso estes recursos já estejam alocados a outros
processos, o processo solicitante deve aguardar pela liberação do mesmo;
5
O deadlock também pode ser representado na forma de grafos dirigidos, onde o processo é
representado por um quadrado e o recurso por um círculo. Quando um processo solicita um
recurso, uma seta é dirigida do quadrado ao círculo. Quando um recurso é alocado a um
processo, uma seta é dirigida do círculo ao quadrado.
Na figura do exemplo, podem-se ver dois processos diferentes (A e B), cada um com um
recurso diferente alocado (R1 e R2). Nesse exemplo clássico de deadlock, é facilmente visível
a condição de espera circular em que os processos se encontram, onde cada um solicita o
recurso que está alocado ao outro processo.
Tratamento de deadlock
As situações de deadlock podem ser tratadas ou não em um sistema, e cabe aos
desenvolvedores avaliar o custo/benefício que essas implementações podem trazer.
Normalmente, as estratégias usadas para detectar e tratar as situações de deadlocks geram
grande sobrecarga, podendo até causar um dano maior que a própria ocorrência do deadlock,
sendo, às vezes, melhor ignorar a situação.
❖ Ignorar a situação
❖ Detectar o deadlock e recuperar o sistema
❖ Evitar o deadlock
Para detectar o deadlock, o sistema deve implementar uma estrutura de dados que armazene as
informações sobre os processos e os recursos alocados a eles. Essas estruturas deverão ser
6
atualizadas dinamicamente, de modo que reflitam realmente a situação de cada
processo/recurso no sistema.
Quanto à detecção do deadlock, vamos apresentar uma das técnicas usadas para detectar a
ocorrência de deadlock em sistemas que possuem vários recursos de cada tipo.
Assim, um processo pode requisitar uma unidade de CD para leitura. Se o sistema possuir duas,
o processo pode utilizar a que estiver disponível, em vez de especificar uma delas. Dessa forma,
o processo solicita o recurso pelo tipo, sem discriminação.
O algoritmo para essa detecção trabalha com duas variáveis, três matrizes unidimensionais
(vetores) e duas matrizes bidimensionais, descritas a seguir:
✓ Estruturas:
n: Variável inteira. Representa a Quantidade de Processos Ativos;
m: Variável inteira. Representa a Quantidade de Tipos de Recursos;
7
Matriz E = : Matriz unidimensional, de tamanho m. Representa a Matriz de
Recursos Existentes;
Note que: ;
5. Preencher a Matriz R com as próximas requisições dos processos, seguindo as
mesmas regras da Matriz C.
8
✓ Faça (para detecção do deadlock):
A lógica do algoritmo é a seguinte: cada processo é considerado como apto a ser executado até
que a detecção prove o contrário. A detecção apenas verifica se os processos requisitam mais
recursos do que estão disponíveis, o que caracteriza um deadlock. Caso o processo requisite
uma quantidade disponível, então ele pode ser executado, e os recursos que foram solicitados
antes podem também ser liberados de volta ao sistema, o que pode permitir que outros
processos também concluam suas execuções e liberem os recursos.
Exemplo do preenchimento das matrizes do algoritmo de detecção de deadlock com vários recursos de
cada tipo.
9
INANIÇÃO
Em programação concorrente, ocorre “inanição” quando um processo nunca é executado,
pois processos de prioridade maior sempre o impedem de ser executado. Em um ambiente
computacional multitarefa, a execução de diversos processos simultâneos deve seguir uma
regra de escalonamento destes para uso do processador. Isso se deve ao fato de que, durante
a mudança de contexto pelo processador, é feita a escolha do próximo processo a ser
executado a partir da prioridade deste. Quando o escalonamento não é feito adequadamente,
pode haver inanição. Uma solução para esta situação é a delegação de um tempo máximo de
espera.
10
A preterição por tempo indeterminado pode ser evitada usando-se uma política de alocação
baseada na regra do primeiro-a-chegar é o primeiro-a-ser-servido. Com esta abordagem, o
processo que espera há mais tempo é o primeiro a receber serviço por parte do recurso liberado.
Fica claro que qualquer dos processos será o mais antigo com o passar do tempo, recebendo,
assim, o direito ao uso do recurso pelo qual estiver esperando.
Outra solução é fazer com que o processo em inanição aumente sua prioridade de acordo com
o seu tempo de espera, o que também pode resolver o problema.
SEMÁFORO
11
Ambas as op e ra ç õe s devem ser realizadas como transacções at óm i c as , isto é, de forma
i ndivis ível , de forma que enquanto umas o p e r a ç õ e s esteja em andamento com um dado
semáforo, nenhuma out ra seja efetuada, g a r a n t i n d o - s e a consistência dos valores das
variáveissemáforo.
A utilização de semáforos permite a sincronização de vários processos, ou
seja, num ambiente o n d e existem diversos processos paralelos competindo
por recursos, o uso de semáforos garante que um dado recurso seja utilizado
de forma sequencial, ou seja, de forma exclusiva.
Os semáforos e as operações sobre eles são usualmente implementadas como chamadas
d o sistema operacional, e representam uma solução para o problema de perda de sinais
de wakeup visto no problema produtor-consumidor. Sendo regiões critica, sua
implementação utiliza i n s t r u ç õ e s tipo TST e, embora provocando uma espera activa, são
operações extremamente rápidas, muito mais eficientes que o uso de outras soluções que
utilizem tais instruções ou as soluções de Dekker ou Peterson.
Semáforos iniciados com valor 1 são conhecidos como semáforos binários [TAN92, p.
42], que permitem que apenas um dentre n processos utilize um dado recurso, ou seja,
garantem o uso individual d e s t e recurso através da exclusão mútua.
Os semáforos são frequentemente utilizados para sincronização de processos, ou seja,
são utilizados para garantir a ocorrência de certas sequencias de eventos ou para impedir
que outras s e q u e n c i a s nunca ocorram ou para que ocorram de uma forma especifica.
No Exemplo 1 temos uma possível so l u ç ã o para o problema do produtor consumidor
utilizando semáforos. Consideramos que as operações P() e V() foram implementadas
como ilustrado no Exemplo 2.10 e incluídas no programa através do cabeçalho
declarado.
12
AtivaProcessoEsperando(s);
} else {
s = *s + 1;
}
}
Exemplo:1 Implementação de operações P() e V() sobre semáforos
MEMÓRIA COMPARTILHADA
#include "semforos.h"
#define FALSE 0
#define TRUE 1
#define N 100
void produtor(void) {
int item;
13
while (TRUE) {
produzir item(&item);
p(&vazio);
p(&mutex);
colocar item(item);
v(&mutex);
v(&cheio);
}
}
void consumidor(void) {
int item;
while (TRUE) {
p(&cheio);
p(&mutex);
remover item(&item);
v(&mutex);
v(&vazio);
consumir item(item);
}
}
14