Sistemas Operacionais:
Sincronização entre
processos
Sincronização
• Programa concorrente
– Executado por diversos processos
– Acesso concorrente a dados
• Paralelismo real x Paralelismo aparente
– Multiprocessadores: paralelismo real
– Paralelismo “aparente”: concorrência
Programas concorrentes
• Processos seqüenciais que executam
concorrentemente (afetam ou são afetados por
outros programas)
• Motivação
– Aumento de desempenho em máquinas
multiprocessadas
– Sobreposição de operações de E/S e
processamento
Problema produtor-consumidor
• Seja um buffer compartilhado entre dois processos. O processo produtor coloca dados em um buffer que são retirados pelo processo consumidor
• Possível implementação
– Uma variável count armazena o número de posições preenchidas no buffer
– Inicialmente armazena o valor 0
– Quando o processo produtor coloca um dado no buffer, count é incrementado
– Quando o processo consumidor retira o dado do buffer, count é decrementado
Produtor
while (true) {
/* produce an item and put in nextProduced */
while (count == BUFFER_SIZE)
; // do nothing
buffer [in] = nextProduced;
in = (in + 1) % BUFFER_SIZE;
count++;
}
Consumidor
while (true) {
while (count == 0)
; // do nothing
nextConsumed = buffer[out];
out = (out + 1) % BUFFER_SIZE;
count--;
/* consume the item in nextConsumed
}
Condição de corrida
• count++ ou count—
– register= count
– register= count+1
– count= register
• Considere a execução intercalada com “count = 5” inicialmente:
S0: produtor executa register1 = count {register1 = 5} S1: produtor executa register1 = register1 + 1 {register1 = 6} S2: consumidor executa register2 = count {register2 = 5} S3: consumidor executa register2 = register2 - 1 {register2 = 4} S4: produtor executa count = register1 {count = 6 } S5: consumidor executa count = register2 {count = 4}
Problema da seção crítica
• Condição de corrida: vários processos
manipulam conjuntos de dados onde o
resultado depende da ordem de execução
• Seção crítica: trecho de código onde somente
um processo pode executar por vez
Solução
• Criar um protocolo que garanta a exclusão
mútua
– Execução da seção crítica por somente um
processo
Propriedades da seção crítica
• Regra 1: Exclusão mútua
• Regra 2: Progressão
– Nenhum processo fora da seção crítica pode bloquear um outro processo
• Regra 3: Espera limitada
– Nenhum processo pode esperar infinitamente para entrar na seção crítica
• Regra 4
– Nenhuma consideração sobre o número de processadores ou velocidades relativas
Alternância
• Desvantagens
– Espera ativa: desperdício de processamento
– Viola a regra 2
While (true){
while (turn !=0);
critical_section();
turn =1;
non_critical_section();
}
While (true){
while (turn !=1);
critical_section();
turn =0;
non_critical_section();
}
Solução de Petersen
• Solução para dois processos
• Assume que as instruções LOAD e STORE são atômicas;
• Os dois processos compartilham duas variáveis:
– int turn;
– boolean flag[2]
• A variável turn indica qual processo deseja entrar na seção crítica.
• O array flag é utilizado para indicar se um processo está pronto para entrar na seção crítica. flag[i] = true significa que o processo Pi está pronto!
Algoritmo para o processo Pi
while (true) {
flag[i] = TRUE;
turn = j;
while ( flag[j] && turn == j);
CRITICAL SECTION
flag[i] = FALSE;
REMAINDER SECTION
}
Seção crítica usando bloqueios
• ...
• ...
• Adquire_bloqueio
– Seção crítica
• Libera_bloqueio
• Seção não-crítica
Sincronização com suporte de HW
• Suporte de HW para implementação de seções
críticas
• Uniprocessadores– desabilitar interrupções
– Executar o código sem preempção
– Ineficiente para multiprocessadores
• Processadores modernos fornecem instruções
atômicas • Atômica = sem interrupções
– Testa a memória e atribui um valor (test and set)
– Troca o conteúdo de duas posições de memória (swap)
Desabilitando interrupções
• Não há troca de contexto nem interrupções por
eventos externos (exceto divisão por 0)
• Desvantagens
– O usuário pode desabilitar o SO
– CLI
– ....
– ...
– STI
Test&Set
boolean TestAndSet (boolean *target)
{
boolean rv = *target;
*target = TRUE;
return rv:
}
Test&Set
• Variável compartilhada: lock., inicializada com false.
• Solução:
while (true) {
while ( TestAndSet (&lock ))
; /* do nothing
// critical section
lock = FALSE;
// remainder section
}
SWAP
void Swap (boolean *a, boolean *b)
{
boolean temp = *a;
*a = *b;
*b = temp:
}
SWAP
• Variável compartilhada Lock inicializada com FALSE; cada processo tem sua variável local key.
• Solução:
while (true) {
key = TRUE;
while ( key == TRUE)
Swap (&lock, &key );
// critical section
lock = FALSE;
// remainder section
}
Semáforos
• Proposto por Dijkstra(1965)
• Sincronização que não necessita de espera ativa
• Semáforo S – variável inteira
• Duas operações S: acquire() e release()
• Operações atômicas
Semáforo
• Binário: varia entre 0 e 1
• Contador: valor inteiro
Implementação de semáforos
• Deve garantir que dois processos não
executem acquire () e release () no mesmo
semáforo ao mesmo tempo
• A implementação do acquire e release torna-
se o problema de seção crítica.
– Espera ativa
• Algumas aplicações podem ficar muito tempo
na seção crítica.
Implementação de semáforo sem
espera ativa • Cada semáforo tem sua fila de espera. Cada
posição da fila de espera tem dois campos:
– valor (tipo inteiro)
– ponteiro para o próximo elemento da lista
Duas operações:
• block – coloca o processo que esta adquirindo
o semáforo na fila apropriada.
• wakeup – remove o processo que esta na fila
de espera e coloca na fila de prontos.
Implementação de semáforo sem
espera ativa • Acquire()
• Release()
Deadlock e starvation
• Deadlock – dois ou mais processsos esperam infinitamente por eventos que
somente podem ser gerados por processos no estado de espera
• Seja S e Q dois semáforos inicializados em 1
P0 P1
S.acquire(); Q.acquire();
Q.acquire(); S.acquire();
. .
. .
. .
S.release(); Q.release();
Q.release(); S.release();
• Starvation – bloqueio indefinido.
Problemas clássicos de
sincronização • Problema de buffer de tamanho limitado
• Problema dos leitores e escritores
• Problema dos filósofos comilões
Buffer de tamanho limitado
• N posições, cada posição armazena um
elemento
• Semáforo mutex inicializado com 1
• Semáforo full inicializado com 0
• Semáforo empty inicializado com N.
Produtor
while (true) {
// produce an item
wait (empty);
wait (mutex);
// add the item to the buffer
signal (mutex);
signal (full);
}
Consumidor
while (true) {
wait (full);
wait (mutex);
// remove an item from buffer
signal (mutex);
signal (empty);
// consume the removed item
}
Problema dos leitores e escritores
• Um conjunto de dados compartilhado entre processos
– Leitores –somente realizam a leitura
– Escritores – podem ler ou escrever
• Problema– permitir múltiplos leitores simultaneamente. Somente um único escritor pode escrever ao mesmo tempo.
• Dados compartilhados
– Buffer
– Semáforo mutex inicializado com 1.
– Semáforo wrt inicializado com 1.
– Inteiro readcount inicializado com 0. Número de leitores lendo o buffer compartilhado
Escritor
while (true) {
wait (wrt) ;
// writing is performed
signal (wrt) ;
}
Leitor
while (true) {
wait (mutex) ;
readcount ++ ;
if (readcount == 1) wait (wrt) ;
signal (mutex)
// reading is performed
wait (mutex) ;
readcount - - ;
if (readcount == 0) signal (wrt) ;
signal (mutex) ;
}
Leitores e escritores
• Nesta solução: o fato de ter um escritor executando,
não impede que outro leitor entre na seção crítica
• Quando um escritor sai da seção crítica signal(wrt), o
novo processo a entrar pode ser tanto um leitor,
quanto um escritor
• Abandono de processos
• Solução alternativa: escritor tem prioridade
– Caso um escritor esteja esperando, nenhum leitor pode
entrar.
Top Related