Post on 29-Sep-2020
Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Capítulo 6: Sincronização de Processos
6.2 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Módulo 6: Sincronização de Processos
Fundamentos
O problema das Regiões Críticas
Solução de Peterson
Hardware de Sincronização
Travas com Mutex
Semáforos
Problemas Clássicos de Sincronização
Monitores
Exemplos de Sincronização
Alternativas
6.3 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Objetivos
Apresentar o conceito de sincronização de processos.
Introduzir o problema da seção crítica, cujas soluções podem ser usadas para garantir a consistência dos dados compartilhados.
Apresentar tanto soluções de software quanto de hardware para o problema da região crítica.
Examinar vários problemas clássicos de sincronização de processos.
Explorar várias ferramentas usadas para resolver problemas de sincronização de processos.
6.4 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Fundamentos
Processos podem ser executados simultaneamente
Pode ser interrompido a qualquer momento, completando parcialmente a execução.
Acesso concorrente a dados compartilhados pode resultar em inconsistências.
Manter a consistência de dados requer a utilização de mecanismos para garantir a execução ordenada de processos cooperantes.
Ilustração do problema:
Suponha que seja desejado fornecer uma solução para o problema do produtor-consumidor que utilize todo o buffer. É possível fazer isso tendo um inteiro count que mantém o número de posições ocupadas no buffer. Inicialmente, count é inicializado em 0. Ele é incrementado pelo produtor após a produção de um novo item e decrementado pelo consumidor após a retirada.
6.5 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Produtor e Consumidor
/* Produtor:produz um item e coloca em nextProduced */
while (true) {
while (count == BUFFER_SIZE)
; // não faz nada
buffer [in] = nextProduced;
in = (in + 1) % BUFFER_SIZE;
count++;
}
/* Consumidor:consome o item em nextComsumed */
while (true) {
while (count == 0)
; // não faz nada
nextConsumed = buffer[out];
out = (out + 1) % BUFFER_SIZE;
count--;
}
6.6 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Condição de Corrida
count++ pode ser implementado como
register1 = count register1 = register1 + 1 count = register1
count– pode ser implementado como
register2 = count register2 = register2 - 1 count = register2
Considere a seguinte ordem de execução com, inicialmente, “count = 5”:
S0: producer execute register1 = count {register1 = 5}S1: producer execute register1 = register1 + 1 {register1 = 6} S2: consumer execute register2 = count {register2 = 5} S3: consumer execute register2 = register2 - 1 {register2 = 4} S4: producer execute count = register1 {count = 6 } S5: consumer execute count = register2 {count = 4}
6.7 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Problema da seção crítica
Considere um sistema de n processos {p0, p1, … pn-1}
Cada processo tem um segmento de código de seção crítica
Os processos podem estar modificando variáveis comuns, atualizndo tabelas, escrevendo arquivos, etc.
Quando um processo estiver em uma seção crítica, nenhum outro processo pode estar nessa mesma seção crítica.
O problema da seção crítica é eleborar um protocolo para resolver isso
Cada processo deve pedir permissão para entrar na seção crítica na seção de entrada, deve seguir a seção crítica até a seção de saída, e então, ir para a seção remanescente.
6.8 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Seção crítica
Estrutura geral de um processo Pi típico:
do {
seção de entrada
seção crítica
seção de saída
seção remanescente
} while (TRUE);
6.9 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Solução para o Problema da Região Crítica
1. Exclusão Mútua - Se um processo Pi está executando sua região crítica, então nenhuma região crítica de outro processo pode estar sendo executada.
2. Progresso - Se nenhum processo está executando uma região crítica e existem processos que desejam entrar nas regiões críticas deles, então a escolha do próximo processo que irá entrar na região crítica não pode ser adiada indefinidamente.
3. Espera Limitada - Existe um limite para o número de vezes que outros processos são selecionados para entrar nas regiões críticas deles, depois que um processo fez uma requisição para entrar em sua região e antes que essa requisição seja atendida.
É assumido que cada processo executa em uma velocidade diferente de zero
Nenhuma hipótese é feita referente à velocidade relativa de execução dos N processos.
6.10 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Manipulação da Seção Crítica no SO
Duas abordagens dependendo se o kernel é preventivo ou não preemptivo
Preemptivo - permite preempção do processo ao executar no modo kernel
Não Preemptivo - executa até sair do modo kernel, bloquear ou render voluntariamente CPU
Essencialmente livre de condições de corrida no modo kernel
6.11 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Tentativas de Solução para a Região Crítica
Inicialmente será considerada uma solução para dois processos
É assumido que as instruções de máquina LOAD (carrega) e STORE (armazena) são atômicas; isto é, não podem ser interrompidas.
Inicialmente são apresentadas duas tentativas e depois a solução
6.12 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Tentativa 1
Variáveis compartilhadas:
var turn: (0..1);inicialmente turn = 0
turn = i Pi pode entrar na sua região crítica
Processo Pi
do {
while turn != i
; /* não faz nada */
// REGIÃO CRÍTICA
turn = j;
// SEÇÃO REMANESCENTE
} while (TRUE);
Satisfaz exclusão mútua, mas não progresso.
6.13 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Tentativa 2
Variáveis compartilhadas:
var flag: array [0..1] of boolean;inicialmente flag [0] = flag [1] = false.
flag [i] = true Pi pronto para entrar na região crítica
Processo Pi
do {
flag[ i ] = true; while ( flag[ j ] )
; /* não faz nada */
// REGIÃO CRÍTICA
– flag [ i ] = false;
// SEÇÃO REMANESCENTE
} while (TRUE);
Satisfaz exclusão mútua, mas não progresso.
6.14 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Solução de Peterson
Os dois processos compartilham duas variáveis: int turn; Boolean flag[2]
A variável turn indica de quem é a vez de entrar na região crítica.
O vetor flag é usado para indicar se um processo está pronto para entrar na região crítica. flag[i] = true significa que processo Pi está pronto!
6.15 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
do {
flag[i] = TRUE;
turn = j;
while ( flag[j] && turn == j)
; /* não faz nada */
// REGIÃO CRÍTICA
flag[i] = FALSE;
// SEÇÃO REMANESCENTE
} while (TRUE);
Algoritmo Para Processo Pi
6.16 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Sincronização por Hardware
Muitos sistemas fornecem suporte de hardware para código de seção crítica
Sistemas Monoprocessados – podem desabilitar interrupções Código em execução pode executar sem preempção Geralmente muito ineficiente em sistemas multiprocessados
Sistemas Operacionais que usam isso não escalam
Arquiteturas modernas fornecem instruções atômicas especiais de hardware
Atômica = não interrompível Testar uma posição de memória e setar um valor Ou trocar conteúdos de duas posições na memória
6.17 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Solução para problemas de seção críticausando Locks
do {
obtém lock
região crítica
libera lock
região remanescente
} while (TRUE);
6.18 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Instrução TestAndSet
Definição:
boolean TestAndSet (boolean *target)
{
boolean ret = *target;
*target = TRUE;
return ret;
}
1. Executa atomicamente
2. Retorna o valor original do valor passado por parâmetro
3. Define o novo valor do parâmetro passado para “verdadeiro”.
6.19 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Solução usando TestAndSet
Variável booleana compartilhada lock, inicializada em FALSE. Solução:
while (true) {
while ( TestAndSet (&lock ))
; /* não faz nada */
// REGIÃO CRÍTICA
lock = FALSE;
// SEÇÃO REMANESCENTE
}
6.20 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Instrução Swap
Definição:
void Swap (boolean *a, boolean *b)
{
boolean temp = *a;
*a = *b;
*b = temp:
}
6.21 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Solução usando Swap
Variável booleana compartilhada lock, inicializada em FALSE; Cada processos tem uma variável booleana key local.
Solução:
while (true) {
key = TRUE;
while ( key == TRUE)
Swap (&lock, &key );
// REGIÃO CRÍTICA
lock = FALSE;
// SEÇÃO REMANESCENTE
}
6.22 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Exclusão Mútua de espera limitadacom TestAndSet()
do {
waiting[i] = TRUE;
key = TRUE;
while (waiting[i] && key)
key = TestAndSet(&lock);
waiting[i] = FALSE;
// critical section
j = (i + 1) % n;
while ((j != i) && !waiting[j])
j = (j + 1) % n;
if (j == i)
lock = FALSE;
else
waiting[j] = FALSE;
// remainder section
} while (TRUE);
6.23 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Travas com Mutex
As soluções anteriores são complicadas e geralmente inacessíveis para programadores de aplicações
Os designers de SO constroem ferramentas de software para resolver o problema da seção crítica
O mais simples é a trava mutex
Para proteger a seção crítica, primeiro adquira a trava com acquire() e depois solte a trava com release(). Variável boleana indicando se a trava está disponível ou não
A chamda para o acquire() e para o release() devem ser atômicas Geralmente implementada através de instruções atômicas do
hardware
Mas essa solução requer espera ocupada Esta trava é, por isso, chamada de spinlock
6.24 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
acquire() e release()
acquire() { while (!disponivel)
; /* espera ocupada */
disponivel = false;
}
release() {
disponivel = true;
}
do {
acquire lock
// seção crítica
release lock
// seção remanescente
} while (true);
6.25 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Semáforo
Ferramenta de sincronização que não requer espera ocupada (busy waiting) Semáforo S – variável inteira Duas operações padrão modificam S: wait() e signal()
Originalmente chamadas P() e V() Menos Complicada Somente pode ser acessada via duas operações indivisíveis (atômicas)
wait (S) {
while (S <= 0)
; // não faz nada
S--;
} signal (S) {
S++;
}
6.26 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Semáforo como uma Ferramenta Geral de Sincronização
Semáforo Contador – valor nele armazenado pode ser qualquer número inteiro.
Semáforo Binário – valor inteiro nele armazenado pode variar entre 0 e 1; pode ser implementado mais simplesmente.
Também conhecido como mutex locks
É possível implementar semáforo contador S como um semáforo binário
Fornece exclusão mútua:
Semaphore mutex; // inicializado em 1
do {
wait (mutex);
// Seção Crítica
signal (mutex);
// restante do código
} while (TRUE);
6.27 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Implementação de Semáforo
Deve garantir que dois processos não possam executar wait () e signal () no mesmo semáforo ao mesmo tempo
Daí, a implementação se torna o problema da região crítica na qual o código do wait e signal são colocados em seções críticas.
Pode ter espera ocupada na implementação da região crítica
Código de implementação é menor
Pequena espera ocupada se região crítica está sendo usada raramente
Observe que aplicações podem perder muito tempo em regiões críticas e daí esta não é uma boa solução.
6.28 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Implementação de Semáforo sem Espera Ocupada
Associar uma fila de espera com cada semáforo. Cada entrada na fila de espera tem dois itens:
valor (de tipo inteiro)
ponteiro para o próximo registro na lista
Duas operações:
block – coloca o processo que evoca a operação na fila de espera apropriada.
wakeup – remove um processo da fila de espera e coloca-o na fila de processos prontos (ready queue).
6.29 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Implementação de Semáforo sem Espera Ocupada (Cont.)
Implementação de wait:
wait(semaphore *S) { S->value--; if (S->value < 0) { adiciona esse processo em “S->list”; block(); }
}
Implementação de signal:
signal(semaphore *S) { S->value++; if (S->value <= 0) { remove o processo P de “S->list”; wakeup(P); }}
6.30 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Deadlock (Impasse) e Starvation (Abandono)
Deadlock – dois ou mais processos estão esperando indefinidamente por um evento que pode ser causado somente por um dos processos esperando o evento
Seja S e Q dois semáforos inicializados em 1
P0 P1
wait (S); wait (Q);
wait (Q); wait (S);
. .
. .
. .
signal (S); signal (Q);
signal (Q); signal (S);
Starvation – bloqueio indefinido. Um processo pode nunca ser removido da fila do semáforo em que está suspensa
Priority Inversion – inversão de prioridade. Problema de escalonamento em que um processo de baixa prioridade mantém um lock necessário para um processo de maior prioridade
6.31 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Problemas Clássicos de Sincronização
Problema do Buffer de tamanho limitado (Bounded-Buffer)
Problema dos Leitores e Escritores
Problema do Jantar dos Filósofos (Dining-Philosophers)
6.32 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Problema do Buffer de Tamanho Limitado
N posições, cada um pode armazenar um item
Semáforo mutex inicializado com o valor 1
Semáforo full inicializado com o valor 0
Semáforo empty inicializado com o valor N
6.33 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Problema do Buffer de Tamanho Limitado (Cont.)
A estrutura do processo produtor
do {
// produz um item
wait (empty);
wait (mutex);
// adiciona o item ao buffer
signal (mutex);
signal (full);
} while (TRUE)
A estrutura do processo consumidor
do {
wait (full);
wait (mutex);
// remove um item do buffer
signal (mutex);
signal (empty);
// consome o item removido
} while (TRUE);
6.34 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Problema dos Leitores e Escritores
Um conjunto de dados é compartilhada entre vários processos concorrentes
Leitores – somente lê um conjunto de dados; eles não realizam nenhuma atualização
Escritores – podem ler e escrever.
Problema – permitir múltiplos leitores ler ao mesmo tempo. Somente um único escritor pode acessar os dados compartilhados ao mesmo tempo.
Dados Compartilhados
Conjunto de dados
Semáforo mutex inicializado em 1.
Semáforo wrt inicializado em 1.
Inteiro readcount inicializado em 0.
6.35 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Problema dos Leitores e Escritores (Cont.)
A estrutura de um processo escritor
do {
wait (wrt) ;
//escrita é feita
signal (wrt) ;
} while (TRUE);
A estrutura de um processo leitor
do {
wait (mutex) ;
readercount ++ ;
if (readercount == 1)
wait (wrt);
signal (mutex);
// leitura é feita
wait (mutex);
readercount - - ;
if (readercount == 0) signal (wrt) ;
signal (mutex) ; } while (TRUE);
6.36 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Problema do Jantar dos Filósofos
Filósofos gastam suas vidas alternando entre pensar e comer
Sem interagir com seus vizinhos, ocasionalmente, eles tentam pegar 2 hashis (um de cada vez) para comer da tigela de arroz
São necesários ambos para comer.
Após comer, eles colocam os hashis novamente na mesa
No caso de 5 filósofos:
Dados compartilhados:
Tigela de arroz (dados compartilhados)
Semáforos chopstick [5] inicializados com 1
6.37 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Problema do Jantar dos Filósofos (Cont.)
A estrutura do Filósofo i:
do {
wait ( chopstick[i] );
wait ( chopStick[ (i + 1) % 5] );
// comer
signal ( chopstick[i] );
signal ( chopstick[ (i + 1) % 5] );
// pensar
} while (TRUE); Qual é o problema desse algoritmo?
6.38 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Problema do Jantar dos Filósofos (Cont.)
Manipulação da seção crítica
Permita que no máximo 4 filósofos estejam sentados simultaneamente à mesa.
Permita que um filósofo pegue os hashis apenas se ambos estiverem disponíveis (a escolha deve ser feito em uma seção crítica).
Use uma solução assimétrica:
O filósofo de numeração ímpar pega primeiro o pauzinho esquerdo e depois o pauzinho direito.
O filósofo de número par pega primeiro o pauzinho direito e depois o pauzinho esquerdo.
6.39 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Problemas com Semáforos
Uso incorreto de operações em semáforos:
signal (mutex) …. wait (mutex)
wait (mutex) … wait (mutex)
Omissão de wait (mutex) ou signal (mutex) (ou ambos)
Deadlock e Starvation podem ocorrer com esses erros.
6.40 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Monitores
Uma abstração de alto nível que fornece um mecanismo conveniente e eficaz para a sincronização de processos
Tipo abstrato de dados, variáveis internas são somente acessíveis por código dentro do procedimento
Somente um processo por vez pode estar ativo no monitor Mas não é poderoso o suficiente para modelar alguns esquemas de
sincronização
monitor nome_do_monitor
{
// declaração de variáveis compartilhadas
procedimento P1 (…) { … }
…
procedimento Pn (…) { … }
codigo_de_inicializacao ( … ) { … }
…
}
6.41 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Visão Esquemática de um Monitor
6.42 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Variáveis Condicionais
condicional x, y;
Duas operações são permitidas em uma variável condicional:
x.wait() – um processo que invoca essa operação é suspenso até x.signal().
x.signal() – retoma um dos processos (se houver) que evocou x.wait().
Se não foi executado x.wait() na variável, então essa ação não tem efeito.
6.43 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Monitor com Variáveis Condicionais
6.44 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Opções de Variáveis Condicionais
Se o processo P chamar x.signal() e o processo Q estiver suspenso em x.wait(), o que deve acontecer a seguir?
Tanto Q quanto P não podem ser executados paralelamente. Se Q for reiniciado, então P deve esperar
Opções incluem:
signal e wait - P espera até que Q deixe o monitor ou aguarde outra condição.
signal e continue - Q espera até que P saia do monitor ou aguarde outra condição.
Ambos têm prós e contras - o implementador deve decidir.
Monitores implementados em Pascal compreendem:
P que executa signal imediatamente sai do monitor, Q é retomado.
Implementado em outras linguagens, incluindo Mesa, C #, Java.
6.45 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Solução para o problema dos Filósofos
monitor DP{ enum {THINKING, HUNGRY, EATING} state[5]; condition self [5];
void pickup (int i) { state[i] = HUNGRY; test(i); if (state[i] != EATING) self [i].wait;
}
void putdown (int i) { state[i] = THINKING;
// test left and right neighbors test((i + 4) % 5); test((i + 1) % 5);
}
void test (int i) { if ( (state[(i + 4) % 5] != EATING) && (state[i] == HUNGRY) && (state[(i + 1) % 5] != EATING) )
{ state[i] = EATING ; self[i].signal () ; }
}
initialization_code() { for (int i = 0; i < 5; i++) state[i] = THINKING;
}}
Cada filósofo evoca as operações pickup() e putdown() na seguinte sequência:
dp.pickup (i) // COMERdp.putdown (i)
6.46 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Exemplos de Sincronização
Solaris
Windows XP
Linux
Pthreads
6.47 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Sincronização no Solaris
Implementa uma variedade de travas (locks) para suportar multitarefa, múltiplas threads (incluindo threads tempo real), e multiprocessamento
Usa mutex adaptativos para eficiência quando está protegendo dados de segmentos com código curto
Usa variáveis condicionais e travas leitores-escritores quando seções longas de código necessitam acessar dados
Usa turnstiles para ordenar a lista de threads esperando para adquirir um mutex adaptativo ou uma trava leitor-escritor
6.48 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Sincronização no Windows XP
Usa máscaras de interrupção para proteger acesso aos recursos globais em sistemas monoprocessados
Usa spinlocks em sistemas multiprocessados
Spinlocking-thread nunca sofrerá preempção.
Também fornece dispatcher objects os quais podem agir como mutexes ou semáforos
Dispatcher objects podem também fornecer eventos
Um evento age de forma parecida com variáveis condicionais
6.49 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Sincronização no Linux
Linux:
Antes do kernel versão 2.6, desabilita interrupções para implementar seções críticas curtas
Versão 2.6 e posterior, totalmente preemptável
Linux provides:
semáforos
inteiros atômicos
spinlocks
versões de leitores-escritores
Em sistemas de uma única CPU, os spinlocks são substituídos pela habilitação e desabilitação da preempção do kernel
6.50 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Sincronização em Pthreads
Pthreads API é independente de SO
Ela fornece:
travas mutex
variáveis condicionais
Extensões não portáveis incluem:
travas de leitura-escrita
spinlocks
6.51 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Alternativas
Memória Transacional
OpenMP
Linguagens de programação funcional
6.52 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Memória transacional Uma transação de memória é uma sequência de operações de
leitura-gravação da memória que são realizadas atomicamente.
void update(){
/* leitura/escrita da memória */}
6.53 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
OpenMP
OpenMP é um conjunto de diretivas de compilação e uma API que dão sporte à programação paralela.
void update(int value){ #pragma omp critical { count += value }}
O código do escopo da diretiva #pragma omp critical é tratado comoseção crítica e é realizado automaticamente.
6.54 Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Linguagens de programação funcional
As linguagens de programação funcional oferecem um paradigma diferente das linguagens procedurais, pois não mantêm o estado.
As variáveis são tratadas como imutáveis e não podem mudar de estado depois de terem sido atribuídos um valor.
Há um crescente interesse em linguagens funcionais como Erlang e Scala por sua abordagem no tratamento de condições de corrida.
Silberschatz, Galvin and Gagne ©2009Operating System Concepts – 8th Edition
Fim do Capítulo 6