• Nenhum resultado encontrado

Aula09 Sincronismo

N/A
N/A
Protected

Academic year: 2021

Share "Aula09 Sincronismo"

Copied!
67
0
0

Texto

(1)

Sistemas Operacionais

(2)
(3)

Aula Passada

• Continuação do Estudo de Escalonamento • Escalonamento Multilevel Queue

• Escalonamento Multilevel Feedback Queue • Escalonamento em Multiprocessadores

• Escalonamento em Tempo Real

(4)
(5)
(6)

Processos Cooperativos

• São processos que compartilham um espaço de

endereço lógico (código e dados) diretamente ou têm permissão para compartilhar dados por meio de arquivos ou mensagens.

(7)

Produtor / Consumidor

Produtor Consumidor

Tanto o produtor quanto o consumidor compartilham uma variável em comum: count

(8)

Produtor / Consumidor

• Considere a seguinte situação:

++count registrador1 = count; registrador1 = registrador1 + 1; count = registrador1; --count registrador2 = count; registrador2 = registrador2 + 1; count = registrador2;

S0: produtor executa registrador1 = count

S1: produtor executa registrador1 = registrador1 + 1 S2: consumidor executa registrador2 = count

S3: consumidor executa registrador2 = registrador2 - 1 S4: produtor executa count = registrador1

S5: consumidor executa count = registrador2

Qual o valor de count ao final da execução? 4

(9)

Pool de Impressão

0 1 2 3 4 5 6 A B abc prog.c prog.n out = 0 in = 3 O processo A lê in e armazena 3 Ocorre uma interrupção

A CPU chaveia para o processo B O processo B lê in e armazena 3 O processo B continua a execução

Armazena o nome do arquivo na posição 3 O processo B atualiza in para 4

O processo A volta a ser executado Verifica a variável local e grava em 3

O processo A atualiza in para 4

3 3

arquivoB.doc

in = 4

(10)

Problemas

• Observe que em tais exemplos que são executados de

forma assíncrona não é possível garantir o valor correto dos dados compartilhados.

• Nós temos várias threads acessando e manipulando

dados ao mesmo tempo.

• O resultado depende da ordem de execução das

threads.

Quando isso acontece chamamos de condição de

(11)

Sincronismo

• As condições de corrida são muito frequentes em

um sistema operacional e devem ser evitadas.

• Devemos garantir que apenas uma thread acesse

a variável count, por exemplo, por vez.

(12)

Seção Crítica

• Primeira técnica para tratar do problema de

sincronismo

• Consistem em identificar um trecho de código

como uma seção crítica

• Cada thread possui seu própria seção crítica • Uma vez que uma thread está executando sua

seção crítica nenhuma outra thread tem permissão de executar sua própria seção crítica

(13)

Seção Crítica

• Os algoritmos devem ter os seguintes requisitos: • Exclusão Mútua: se um processo está na seção

crítica, nenhum outro processo pode entrar nela

• Progresso: se nenhum processo está na seção

crítica, nenhum processo que queira entrar em sua seção crítica não pode esperar indefinitivamente.

• Espera limitada: se um processo deseja entrar na

seção crítica, existe limite em termos de números de processos que pode fazer isto antes dele.

(14)

Seção Crítica

entering critical section

leaving critical section

critical section

non critical section

Essa parte do código só pode ser executada apenas por uma thread

(15)

Seção Crítica

Processo A

Processo B

A entra na região crítica A deixa a região crítica

B tenta entrar na região crítica

B entra na

região crítica região críticaB deixa a

(16)

Seção Crítica - Algoritmo 1

• Considerando 2 threads:

As threads compartilham uma variável turn,

inicializadas com 0 ou 1.

Se turn == i, então a Thread i tem permissão

(17)

Seção Crítica - Algoritmo 1

Implementação do
 Algoritmo 1

Implementação da
 Thread

(18)

Seção Crítica - Algoritmo 1

• O problema dessa solução é que ela garante a

exclusão mútua mas não garante o progresso

• Nessa solução a thread não guarda informação

suficiente sobre o estado de cada thread. Ela sabe apenas qual thread deve entrar na seção crítica.

(19)

Seção Crítica - Algoritmo 2

• Para solucionar o problema anterior, vamos

considerar duas variáveis booleanas: flag0 e flag1

Cada variável é inicializara com o valor false. Se

uma das variáveis for true, isso significa que a

thread em questão está pronta para entrar em sua seção crítica.

(20)

Seção Crítica - Algoritmo 2

(21)

Seção Crítica - Algoritmo 2

• Essa abordagem apesar de resolver o problema

citado no Algoritmo 1 ainda não garante a condição de progresso.

Imagine que T0 define flag0 como true

Antes de executar o loop while, a thread T1 define a

flag1 como true!

• O que acontece?

(22)

Seção Crítica - Algoritmo 3

• Para evitar tais problemas e garantir os requisitos

apresentados anteriormente, a solução é combinar o Algoritmo 1 e 2 em uma só solução

• A threads irão compartilhar as 3 variáveis: • boolean flag0;

• boolean flag1 • int turn;

(23)

Seção Crítica - Algoritmo 3

(24)

Seção Crítica - Algoritmo 3

• Essa solução atende os três requisitos ditos

anteriormente: exclusão mútua, progresso e espera limitada.

• Qual o problema das abordagens apresentadas

até aqui?

(25)
(26)

Semáforo

• Mecanismo de sincronização que não requer espera ocupada • Proposto por Dijkstra

• Semáforo é uma variável inteira (S) que é acessada apenas por

duas operações:

acquire( ), P( ), wait( ), up( );

release( ), V ( ), signal( ), down( );

• Essas operações devem ser atômicas: é garantido que, uma

vez iniciada a operação, nenhum outro processo tem acesso ao semáforo.

(27)

Semáforo

(28)

Semáforo

• Podemos implementar uma semáforo de duas formas: • Semáforo binário: só pode variar entre 0 e 1

• conhecido como mutex

• Semáforo contador: pode variar em N valores

inteiros positivos

• A implementação de semáforo aqui apresentada

ainda apresenta o problema de espera ocupada. Como resolver?

(29)

Semáforo

• Para resolver tal problema, devemos modificar nossa

implementação de acquire() e release()

• Devemos implementar tais funções utilizando o bloqueio de

processos e o wakeup ao invés de um loop.

• De forma geral, temos:

acquire(S) { value--;

if(value < 0) {

acrescenta esse processo a lista block; } } release(S) { value++; if(value <= 0) {

remove o processo P da lista wakeup(P)

} }

(30)

Semáforo

• Um aspecto crítico do semáforo é que as funções de

acquire() e release() devem ser executadas atomicamente.

• Podemos fazer isso de duas formas:

• Em um ambiente monoprocessado, podemos

desabilitar interrupções

• Podemos aplicar uma solução de seção crítica, onde

(31)

Semáforos


Deadlocks e Starvation

• Deadlocks: dois ou mais processos ficam

esperando um recurso que só pode ser fornecido por um processo que está bloqueado.

• Starvation: bloqueio indefinido. Um processo pode

nunca ser removido da fila de semáforos na qual está bloqueado.

(32)

Semáforo

Deadlocks e Starvation

P0 P1 acquire(S) acquire(Q) acquire(Q) acquire(S) . . . . . . release(S) release(Q) release(Q) release(S)

(33)

Problemas Clássicos de

Sincronismo

• O problema do bounded buffer

• O problema dos leitores-escritores • O problema do jantar dos filósofos • O problema do barbeiro dorminhoco

(34)

Bounded Buffer

produtor

consumidor

Como resolver esse problema com semáforos? Buffer

(35)

Bounded Buffer

• Solução com três semáforos

• mutex: semáforo binário

• empty e full: semáforos contador • mutex: permite a exclusão mútua

• empty e full: contam o número de buffers vazios e

(36)

Leitores e Escritores

Banco de Dados

Leitores Escritores Qual o problema envolvendo


(37)

Leitores e Escritores

• Qual o problema de sincronismo nesse caso?

• Se dois leitores acessarem concorrentemente na

há problema de sincronismo

• No entanto, se um leitor e um escritor acessar o

banco ao mesmo tempo, devemos tratar o sincronismo

• Os escritores devem ter acesso exclusivo ao

(38)

Leitores e Escritores

• Podemos tratar esse problema de duas maneiras:

• Nenhum leitor seja mantido esperando, a menos que um

escritor já tenha obtido permissão para usar o banco de dados compartilhado

• Quando um escritor estiver pronto, ele realiza sua escrita o

mais breve possível.

• Ambos tem problema de starvation: • primeiro caso: com os escritores • segundo caso: com os leitores

(39)
(40)
(41)
(42)
(43)

Jantar dos filósofos

• Problema:

• O filósofo ou pensa ou come

• Enquanto ele está pensando, ele não interage com os demais

colegas

Quando um filósofo ele sente fome, ele pega o hashi da

direita, pega o hashi da esquerda e come sem largar os hashi

Ao terminar coloca os hashi de volta na mesa e recomeça a

pensar

(44)

Jantar dos filósofos

• Cada hashi é um semáforo

Tenta pegar um hashi: acquire() Solta o hashi: release()

• Qual o problema dessa abordagem? • Possibilidade de deadlock

(45)

Atividade

• Implemente o problema dos filósofos propondo uma

solução para o deadlock.

Entrega: 20/12/12 às 23:59:59 via SIGAA ou via

(46)
(47)

Monitores

• Tanto a seção crítica quando os semáforos são

técnicas que estão sujeitas a erro de programação

• Esses erros podem afetar o funcionamento da

sincronização, impedindo que a exclusão mútua seja garantida

• Os monitores foram propostos com a finalidade de

evitar tais problemas

(48)

Monitores

• Criado por Dijkstra (1971) e desenvolvidos por Hoare e Hansen

(1974/75).

• É um tipo de dado abstrato que encapsula variáveis e métodos

que são compartilhado entre vários processos

• Um tipo monitor apresenta um conjunto de operações definidas

pelo programador, que recebem exclusão mútua dentro do monitor.

• Um procedimento dentro do monitor só pode acessar variáveis

dentro do monitor e os parâmetros passados.

• Da mesma forma, variáveis locais do monitor só pode ser

(49)

Monitores

• A própria implementação do monitor proíbe o

acesso simultâneo a procedimento definidos dentro do monitor.

• Somente um processo pode estar ativo dentro do

monitor

• O programador não precisa explicitar esse

sincronismo, ele já está embutido no próprio monitor

• O código da seção crítica não precisa ser mais

(50)

Monitores

• Quando um processo chama um procedimento em um

monitor, o primeiro passo é verificar se existe algum outro processo ativo no monitor:

• Sim: o processo atual é suspenso

• Não: o processo atual pode executar sua seção crítica • A sincronização é feita por meio de operações de WAIT e

SIGNAL

Um processo (ou thread) que chama x.wait é suspenso até que outro processo chame x.signal

(51)
(52)
(53)

Implementando

(54)

yield()

Quando uma thread chama o método yield ela continua no modo executável,

mas permite que a JVM selecione outra thread executável para ser executada.

• É mais eficiente que a espera ocupada, mas pode acontecer o que

(55)

synchronized()

Cada objeto java possui o que chamamos de lock • Normalmente, esse lock é ignorado a não ser que

especifique o método como sincronizado

• Exemplo:

public synchronized void insert(Object item) {
 …


(56)

synchronized()

Podemos definir os métodos insert e

remove do produtor-consumidor com o synchronized.

• Essa solução evita que duas threads

modifiquem o valor da variável count

• Se uma thread estiver executando um

método synchronized, ela obtém a posse do lock.

Apenas a thread que tem posse do

lock pode executar sua seção crítica

Outra thread não pode ter acesso ao

lock, logo fica aguardando até

executar o seu método synchronized

(57)

synchronized()

Apesar desse método garantir que apenas uma thread executará a seção

crítica, o synchronized não soluciona o problema de deadlock

Um deadlock pode acontecer quando o buffer está cheio, o consumidor esteja

dormindo e o produtor deseja inserir no buffer:

Produtor entra no método insert (o lock está disponível) , verifica que o

buffer está cheio e chama o método yield

O produtor fica em um estado de espera, mas ainda possui o lock do objeto • Quando o consumidor acordar será impossibilitado de continuar, já que:

• O produtor está bloqueado esperando que o consumidor libere espaço

no buffer

O consumidor está bloqueado esperando que o produtor libere o lock Qual a solução?

(58)

wait() e notify()

• Cada objeto java possui também um conjunto de

espera (set wait) que juntamente com o lock permite o sincronismo

• Quando não é possível continuar a execução da

thread por conta de alguma condição, essa thread libera o lock e é colocada neste conjunto de

espera, evitando o deadlock.

Esse controle é feito utilizando os métodos wait() e

(59)
(60)

wait() e notify()

Wait:!

• A thread libera o lock do objeto

• O estado da thread é definido como bloqueado

• A thread é colocada no conjunto de espera do objeto

Notify:!

• Apanha uma thread T qualquer na lista de threads do conjunto de

espera

• Move T do conjunto de espera para o conjunto de entrada • Define o estado de T de bloquado para executável

(61)

notifyAll()

• Qual o problema com a chamada do método

notify?

• Ela funciona bem quando você tem apenas um

thread no seu conjunto de espera.

• Imagine o seguinte cenário:

• Várias threads no conjunto de espera com

diferentes condições para retomarem a execução

(62)

notifyAll()

• Considere 5 threads (T1, T2, T3, T4, T5) e uma

variável compartilhada turn

turn indica qual tread deve ser executada

Quando uma thread deseja realizar um trabalho ela

(63)

notifyAll()

• Essa abordagem pode causar

deadlock

• Em um determinado estado

todas as threads podem está no wait()

• O problema é que a chamada

notify seleciona

arbitrariamente alguma thread do conjunto de espera.

• O programado não tem

controle sobre qual thread será escolhida

(64)

notifyAll()

O uso da chamada notifyall() no lugar da notify()

soluciona tal problema

A chamada notifyall() permite notificar todas as

threads do conjunto de espera

• Ou seja, o método notifyall permite despertar todas

as threads que estão em espera e permite que elas decidam quem vai executar.

(65)

synchronized() 


(em bloco)

Java permite utilizar o synchronized em um bloco de

código

• Útil para métodos que possuem apenas uma pequena

(66)
(67)

Referência

Referências

Documentos relacionados

Esta seção é fundamental, pois apresenta uma das principais características da ciência: parcimônia e crítica, ou seja, o pesquisador deve ser a pessoa mais crítica

Em conformidade com o artigo 13.º do RJEC o júri do concurso tem a seguinte composição: Presidente Professora Doutora Manuela Pintado; Outros membros do

Diâmetro a altura do peito (DAP) das plantas de teca (cm) durante o experimento até os 30 map, em função das interações de P x K para análise de variância e

RESUMO: Na região do Cerrado brasileiro, as principais culturas de cobertura utilizadas para a produção de palhada no sistema de plantio direto são as braquiárias (Brachiaria

a seguir, na seção 5, é apresentada a implementação do problema clássico de sincronismo do jantar dos filósofos com monitores em Java e STM em Haskell; por fim, na seção 6

69 Linha de Chegada Francisquence

Figura 82 – Imagem BSD de microscópio eletrônico de varredura com exemplos de grãos de xenotímio do ortognaisse Resende Costa onde é possível de se observar as diversas

GESSICA FERNANDA DE OLIVEIRA DIAS