Você está na página 1de 14

SINCRONIZAÇÃO ENTRE PROCESSOS/SEMÁFOROS

A sincronização entre processos permite gerenciar o acesso concorrente a recursos do


sistema operacional de forma controlada por parte dos processos, de maneira que um recurso
não seja modificado em simultâneo, ou que os processos não fiquem em espera que o recurso
seja libertado.

Os processos "aplicativos ou programas" de um computador compartilham determinados


recursos da chamada região crítica, que são as variáveis globais, as instruções de E/S, algum
banco de dados. Neste compartilhamento podem ocorrer erros.

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:

Matrícula finalizada -1 (5-1)=4

Matrícula finalizada -1 (5-1)=4

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.

Abaixo temos soluções para problemas de sincronização de processos (e/ou threads).

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.

No entanto, há diversos problemas associados à esta operação:

- 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

O Deadlock "interbloqueio, blocagem, impasse", em sistemas operacionais "SO", refere-se


a uma situação em que ocorre um impasse, e dois ou mais processos ficam impedidos de
continuar suas execuções, ou seja, ficam bloqueados, esperando uns pelos outros. Trata-se de
um problema bastante estudado em sistemas operacionais e banco de dados, pois é inerente à
própria natureza desses sistemas.

O deadlock ocorre com um conjunto de processos e recursos não-preemptíveis, onde um ou


mais processos desse conjunto está aguardando a liberação de um recurso por um outro
processo, o qual, por sua vez, aguarda a liberação de outro recurso alocado ou dependente do
primeiro processo.

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;

❖ Condição de não-preempção: recursos já alocados a processos não podem ser


tomados à força. Eles precisam ser liberados explicitamente pelo processo que detém a
sua posse;
❖ Condição de exclusividade mútua: cada recurso ou está alocado a um processo ou
está disponível;
❖ Condição de posse-e-espera: cada processo pode solicitar um recurso, ter esse recurso
alocado para si e ficar bloqueado, esperando por um outro recurso;
❖ Condição de espera circular: deve existir uma cadeia circular de dois ou mais
processos, cada um dos quais esperando por um recurso que está com o próximo
integrante da cadeia.

Representação de deadlock em gráficos

Exemplo: Representação de deadlock em grafos de alocação de recursos, com dois


processos A e B, e dois recursos R1 e R2.

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.

Existem três estratégias para tratamento de deadlocks:

❖ Ignorar a situação
❖ Detectar o deadlock e recuperar o sistema
❖ Evitar o deadlock

I. Algoritmo do avestruz (ignorar a situação)


A estratégia mais simples para tratamento (ou não) do "deadlock", conhecida como Algoritmo
do Avestruz, é simplesmente ignorá-lo. Muitos defendem que a frequência de ocorrência deste
tipo de evento é baixa demais para que seja necessário sobrecarregar a CPU com códigos extras
de tratamento, e que, ocasionalmente, é tolerável reiniciar o sistema como uma ação corretiva.

II. Detectar o deadlock e recuperar o sistema


Nessa estratégia, o sistema permite que ocorra o deadlock e só então executa o procedimento
de recuperação, que se resume na detecção da ocorrência e na recuperação posterior do sistema.
É na execução desse procedimento que ocorre a sobrecarga, pois existem dois grandes
problemas: primeiramente, como/quando detectar o deadlock e depois, como corrigi-lo.

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.

Só o mero procedimento de atualização dessas estruturas já gera uma sobrecarga no sistema,


pois toda vez que um processo aloca, libera ou requisita um recurso, as estruturas precisam ser
atualizadas.

Além disso, o SO precisa verificar a ocorrência da condição de espera circular nessas


estruturas para a efetiva detecção do deadlock. Esse procedimento, por sua vez, gera outra
sobrecarga, que pode ser mais intensa se não for definido um evento em particular para ser
executado, como a liberação de um recurso, por exemplo. Assim, ou o SO verifica
periodicamente as estruturas (o que não é aconselhável, pois pode aumentar consideravelmente
o tempo de espera dos processos não-bloqueados), ou pode-se implementar uma política, onde
o SO verifica as estruturas quando o mesmo realizar algum procedimento de manutenção do
sistema, por exemplo.

Finalmente, só após detectar a presença do deadlock no sistema, o SO precisa corrigi-lo,


executando um procedimento de recuperação.

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.

III. Detecção de deadlock com vários recursos de cada tipo

O algoritmo de detecção de deadlock com vários recursos de cada tipo baseia-se em um


ambiente que possua vários recursos do mesmo tipo e os processos solicitam apenas pelo tipo
de recursos, não especificando qual recurso desejam utilizar.

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;

Matriz A = : Matriz unidimensional, de tamanho m. Representa a Matriz de


Recursos Atualmente Disponíveis;

Matriz W = : Matriz unidimensional, de tamanho m. Representa uma Matriz


Auxiliar, presente somente para facilitar o cálculo durante a execução do algoritmo;

Matriz C = : Matriz bidimensional, de tamanho n x m. Representa a Matriz de


Alocação Corrente;

Matriz R = : Matriz bidimensional, de tamanho n x m. Representa a Matriz de


Recursos Requisitados.

✓ Faça (para preenchimento das estruturas):

1. Preencher a Matriz E com as quantidades de instâncias de cada tipo de recurso;


2. Preencher a Matriz C com as quantidades de instâncias de cada tipo alocadas aos
processos, sendo que o somatório de cada coluna da Matriz C deve ser menor ou
igual à quantidade do recurso correspondente na Matriz E (os processos nunca
podem requisitar mais recursos que existentes no sistema);
3. Preencher a Matriz W com o resultado da subtração da quantidade de cada recurso
da Matriz E com o valor do somatório de cada coluna do recurso correspondente
da Matriz C, ou seja:
4. Preencher inicialmente a Matriz A com os valores da Matriz W.

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):

1. Inicialmente, desmarcar todos os processos;


2. Para um processo Pi desmarcado, verificar se todos os elementos da linha i na
Matriz R são menores ou iguais aos da Matriz A;
3. Se for, então marque o execute o processo Pi e libere os recursos requisitados pelo
processo na Matriz C (adicionar a linha i da Matriz C na Matriz A);
4. Retornar ao passo 2.

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.

Um exemplo do preenchimento das matrizes encontra-se na figura abaixo, considerando-


se n=2 e m=3.

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.

A ocorrência da inanição se dá quando os programas rodam indefinidamente (razão pela qual


também se dá o nome de preterição indefinida a esta situação) e não fazem nenhum progresso
em seu processamento, ao contrário do deadlock, que ocorre quando os processos permanecem
bloqueados, dependendo da liberação dos recursos por eles alocados.

Em um sistema dinâmico, as requisições de recursos ocorrem durante todo o tempo. Algumas


políticas são necessárias para subsidiar a decisão de quem vai ficar com qual recurso e em que
momento. Essas políticas, apesar de parecerem extremamente razoáveis, podem fazer com que
alguns processos nunca sejam servidos, apesar de não estarem em deadlock.

Um algoritmo possível para implementar a alocação da impressora é o que escolhe o processo


com o menor arquivo a ser impresso (assumindo que esta informação está disponível). Este
algoritmo maximiza o número de usuários satisfeitos com o sistema, e parece ser um algoritmo
justo. Consideremos, no entanto, o que acontece num sistema muito carregado, quando
determinado processo tem um arquivo imenso a ser impresso. Cada vez que a impressora
estiver disponível, o sistema vai escolher, para usar a impressora, o processo com o menor
arquivo a ser impresso. Se houver um fluxo constante de processos no sistema com arquivos
pequenos, aquele ou aqueles com arquivos grandes jamais poderão usar a impressora. Eles
apenas “morrerão de fome” (como o próprio sentido da palavra inanição estabelece), ou seja,
serão preteridos indefinidamente em favor de outros, como se estivessem bloqueados.

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

Para resolver o problema produtor-consumidor, Dijkstra propôs também


em1965 a utilização de variáveis inteiras para controlar o número de sinais
wakeup para uso futuro [DEI92, p.89]. Estas v a r i á v e i s foram denominadas
“semáforos”, e sobre elas estabeleceu-se duas diferentes o p e r a ç õ e s : P
(conhecida também como Down) e V (conhecida também como Up), que
são generalizações das operações sleep e wakeup e funcionam c o m o mostram
a Tabela 1.

Tabela 1: Operações P () e V () sobre semáforos


Operações
P(X) ou Down(X)
Verifica se o valor do semáforo X é positivo (X>0).
Caso afirmativo decrementa X de uma unidade (ou seja, consome um sinal de wakeup).
Caso negativo envia sinal de sleep,fazendo inactivo o processo.
Operação
V (X) ou Up (X)
Incrementa o valor do semáforo X de uma unidade.
Existindo processos inactivos, na ocorrência uma operação down, um deles é escolhido
aleatoriamente pelo sistema para ser activado
(semáforo retornará a zero, i.e., X=0).

A implementação de semáforos em linguagem C poderia ser realizada


como esquematizada no Exemplo 1.

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.

Typedef int semaforo;


void P(semaforo *s) {
if(*s > 0) {
s = *s - 1;
} else {
sleep();
}
}
void V(semaforo *s) {
if (ExisteProcessoEsperando(s)) {

12
AtivaProcessoEsperando(s);
} else {
s = *s + 1;
}
}
Exemplo:1 Implementação de operações P() e V() sobre semáforos

MEMÓRIA COMPARTILHADA

A memoria compartilhada é um mecanismo f r e q u e n t e m e n t e utilizado para a


comunicação entre processos diferentes onde uma região de memoria e reservada para
uso comum dos processos envolvidos na comunicação.
A área de memória reservada para os processos é semelhantes a um buffer, mas nesta
situação todos os processos envolvidos podem escrever e ler neste buffer. Como dois
processo não podem a cessar simultaneamente esta região de memória, e m b o r a
c o m p a r t i l h a d a seu uso deve ser serializado, o u seja, um processo de cada vez deve
utilizar a região de memória d e s t i n a d a a comunicação entre eles. Para obter-se i s s o ,
associa-se um semáforo com valor inicial 1 à região de memória. Todo os processos, antes
de usarem a área de memória devem accionar uma operação P(). Ao finalizar o uso
deverão accionar uma operação V(), garantindo seu uso exclusivo.
De forma geral, a solução é semelhante aquela do problema de produtores e consumidores,
aplicada a n processos que podem tanto ler ou escrever numa região de memória especı́fica,
sendo frequentemente utilizada para passagem de mensagens entre processos.

#include "semforos.h"

#define FALSE 0
#define TRUE 1
#define N 100

typedef int semaforo;


semaforo mutex = 1;
semaforo vazio = N;
semaforo cheio = 0;

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);
}
}

Exemplo 2. Solução do problema produtor-consumidor com semáforos.

14

Você também pode gostar