Módulo 6: Sincronização de Processos Fundamentos Fundamentos O problema das regiões críticas Hardware de Sincronização Semáforos Problemas Clássicos de Sincronização Regiões Críticas Monitores Sincronização no Solaris 2 Transações Atômicas 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. Soluções de memória compartilhada com área de armazenamento limitada (bounded-buffer capítulo 4) permite no máximo n 1 itens no buffer ao mesmo tempo. Uma solução, na qual N buffers são utilizados não é simples. Suponha que sejam feitas modificações no código do produtor-consumidor adicionando uma variável counter, inicializada em 0 e incrementada cada vez que um novo item é adicionado ao buffer 6.1 6.2 Bounded-Buffer Bounded-Buffer (Cont.) Dados compartilhados type item = ; var buffer array [0..n-1] of item; in, out: 0..n-1; counter: 0..n; in, out, counter := 0; Processo Produtor produz um item em nextp while counter = n do no-op; buffer [in] := nextp; in := in + 1 mod n; counter := counter +1; Processo consumidor while counter = 0 do no-op; nextc := buffer [out]; out := out + 1 mod n; counter := counter 1; consume um item em nextc Os comandos: counter := counter + 1; counter := counter - 1; Devem ser executados atomicamente. 6.3 6.4
O Problema das Regiões Críticas n processos todos competindo para utilização dos mesmos dados compartilhados Cada processo tem um segmento de código, chamado região crítica, no qual os dados compartilhados são acessados. Problema garantir que quando um processo está executando sua nenhum outro processo tem permissão de executar a mesma região. Estrutura do Processo P i código de entrada código de saída Solução para o Problema da Região Crítica 1. Exclusão Mútua. Se um processo Pi está executando sua, então nenhuma de outro processo pode estar sendo executada. 2. Progresso. Se nenhuma está sendo executada e existem processos em espera para entrar em suas regiões críticas, apenas esses processos podem ser selecionados para entrar em suas regiões críticas e essa seleção 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 em suas regiões críticas, depois que um processo fez uma requisição para entrar em sua região, e antes que essa requisição seja atendida. 1. É assumido que cada processo executa em uma velocidade diferente de zero 2. Nenhuma hipótese é feita referente à velocidade relativa de execução dos n processos. 6.5 6.6 Tentativas Iniciais de Resolver o Problema Algoritmo 1 Somente 2 processos, P 0 e P 1 Estrutura Geral do processo P i (outro processo P j ) código de entrada código de saída Processos podem compartilhar algumas variáveis comuns para sincronizar suas ações. Variáveis compartilhadas: var turn: (0..1); inicialmente turn = 0 turn - i P i pode entrar na sua Processo P i while turn i do no-op; turn := j; Satisfaz exclusão mútua, mas não progresso. 6.7 6.8
Algoritmo 2 Variáveis compartilhadas: var flag: array [0..1] of boolean; inicialmente flag [0] = flag [1] = false. flag [i] = true P i pronto para entrar na Processo P i flag[i] := true; while flag[j] do no-op; flag [i] := false; Satisfaz exclusão mútua, mas não progresso. Algoritmo 3 Combinar variáveis compartilhadas dos algoritmos 1 e 2. Processo P i flag [i] := true; turn := j; while (flag [j] and turn = j) do no-op; flag [i] := false; Atende os três requisitos; resolve o problema de seção crítica para dois processos. 6.9 6.10 Algoritmo para Diversos Processos Algoritmo para Diversos Processos (Cont.) Antes de entrar na sua, processos recebem um número. Quem possuir o menor número entra primeiro. Se processos P i e P j recebem o mesmo número, se i < j, então P i é atendido primeiro; senão P j é atendido primeiro. O esquema de numeração sempre gera números em ordem crescente; ex.: 1,2,3,3,3,3,4,5... Conhecido também como algoritmo de agências bancárias (bakery algorithm) Notação < ordem lexográfica (número do ticket, número do proc.) (a,b) < (c,d) se a < c ou se a = c e b < d max (a 0,, a n-1 ) é um número k, tal que k a i, para i = 0,, n 1 Dados compartilhados var choosing: array [0..n 1] of boolean; number: array [0..n 1] of integer, Estruturas de dados são inicializadas em false e 0 respectivamente 6.11 6.12
Algoritmo para Diversos Processos (Cont.) Hardware de Sincronização choosing[i] := true; number[i] := max(number[0], number[1],, number [n 1])+1; choosing[i] := false; for j := 0 to n 1 do begin while choosing[j] do no-op; while number[j] 0 and (number[j],j) < (number[i], i) do no-op; number[i] := 0; Testar e modificar o conteúdo de uma área de memória atomicamente. function Test-and-Set (var target: boolean): boolean; begin Test-and-Set := target; target := true; 6.13 6.14 Exclusão Mútua usando Test-and-Set Semáforos Dados Compartilhados: var lock: boolean (initially false) Processo P i while Test-and-Set (lock) do no-op; lock := false; Ferramenta de sincronização que não requer espera ocupada (busy waiting). Semáforo S variável inteira Somente pode ser acessada via duas operações indivisíveis (atômicas) wait (S): while S 0 do no-op; S := S 1; signal (S): S := S + 1; 6.15 6.16
Exemplo: Seção Crítica com n Processos Implementação de Semáforos Variáveis compartilhadas var mutex : semaphore initially mutex = 1 Processo P i signal(mutex); Definição de semáforo é feita através de um registro type semaphore = record value: integer L: list of process; São assumidas duas operações simples: block: suspende o processo que a evoca. wakeup(p): restaura a execução de um processo bloqueado P. 6.17 6.18 Implementação (Cont.) Operações sobre semáforos podem ser definidas como: wait(s): S.value := S.value 1; if S.value < 0 then begin signal(s): S.value := S.value = 1; if S.value 0 then begin adiciona este processo a S.L; block; remove o processo P de S.L; wakeup(p); Semáforos Empregados para Sincronização Execute B em P j somente após A executar em P i Use semáforo flag inicializado em 0 Código: P i A signal(flag) P j wait(flag) B 6.19 6.20
Deadlock (Impasse) e Starvation (Abandono) Dois tipos de Semáforos 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 P 0 P 1 wait(s); wait(q); wait(q); wait(s); Semáforo Contador valor nele armazenado pode ser qualquer número inteiro. Semáforo Binário valor nele armazenado pode variar entre 0 e 1; pode ser implementado mais simplesmente. É possível implementar um semáforo contador S como um semáforo binário. 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 devido a um mecanismo de seleção injusta. 6.21 6.22 Implementando S como um Semáforo Binário Implementando S (Cont.) Estruturas de dados: Inicialização: var S1: binary-semaphore; S2: binary-semaphore; S3: binary-semaphore; C: integer; S1 = S3 = 1 S2 = 0 C = valor inicial do semáforo S operação wait operação signal wait(s3); wait(s1); C := C 1; if C < 0 then begin signal(s1); wait(s2); end else signal(s1); signal(s3); wait(s1); C := C + 1; if C 0 then signal(s2); signal(s)1; 6.23 6.24
Problemas Clássicos de Sincronização Problema Bounded-Buffer Problema do Buffer de tamanho limitado (Bounded-Buffer) Problema dos Leitores e Escritores Problema dos Filósofos Dados compartilhados type item = var buffer = full, empty, mutex: semaphore; nextp, nextc: item; full :=0; empty := n; mutex :=1; 6.25 6.26 Problema Bounded-Buffer (Cont.) Problema Bounded-Buffer (Cont.) Processo produtor produz um item em nextp wait(empty); signal(mutex); signal(full); Processo consumidor wait(full) remove um item de buffer para nextc signal(mutex); signal(empty); consome o item em nextc 6.27 6.28
Problema dos Leitores e Escritores Problema dos Leitores e Escritores(Cont.) Dados compartilhados var mutex, wrt: semaphore (=1); readcount : integer (=0); Processo Escritor wait(wrt); realização da escrita signal(wrt); Processo Leitor readcount := readcount +1; if readcount = 1 then wait(wrt); signal(mutex); realização da leitura readcount := readcount 1; if readcount = 0 then signal(wrt); signal(mutex): 6.29 6.30 Problema dos Filósofos Problema dos Filósofos (Cont.) Dados compartilhados var chopstick: array [0..4] of semaphore; (=1 initially) Filósofo i: wait(chopstick[i]) wait(chopstick[i+1 mod 5]) comendo signal(chopstick[i]); signal(chopstick[i+1 mod 5]); pensando 6.31 6.32
Regiões Críticas Regiões Críticas (Cont.) Construção para sincronização em alto nível Uma variável compartilhada v do tipo T, é declarada como: var v: shared T Variável v é acessada somente através do comando region v when B do S onde B é uma expressão Booleana. Enquanto o comando S está sendo executado, nenhum outro processo pode acessar a variável v. Regiões que referem a mesma variável compartilhada excluem umas as outras no tempo. Quando um processo tenta executar o comando region, a expressão Booleana B é avaliada. Se B é true, comando S é executado. Se é falso, o processo é atrasado até B tornar-se true e nenhum outro processo estiver na região associada com v. 6.33 6.34 Exemplo Bounded Buffer Bounded Buffer (Cont.) Variáveis compartilhadas: var buffer: shared record pool: array [0..n 1] of item; count,in,out: integer Processo produtor insere nextp no buffer compartilhado region buffer when count < n do begin pool[in] := nextp; in:= in+1 mod n; count := count + 1; Processo consumidor remove um item do buffer compartilhado e põem em nextc region buffer when count > 0 do begin nextc := pool[out]; out := out+1 mod n; count := count 1; 6.35 6.36
Implementação: region x when B do S Implementação (Cont.) Associa com a variável compartilhada x, as seguintes variáveis: var mutex, first-delay, second-delay: semaphore; first-count, second-count: integer, Acesso mutuamente exclusivo à é possibilitado pela variável mutex. Se um processo não pode entrar na por causa da expressão booleana B, ele inicialmente espera no semáforo firstdelay (primeira espera); ele então é movido para o semáforo second-delay (segunda espera) antes de poder reavaliar B. Manter controle do número de processos esperando nos semáforos first-delay e second-delay, com variáveis first-count e second-count respectivamente. O algoritmo assume uma ordenação FIFO na fila de processos esperando por um semáforo. Para uma disciplina arbitrária de fila, uma implementação mais complicada é necessária. 6.37 6.38 while not B do begin first-count := first-count + 1; if second-count > 0 then signal(second-delay) else signal(mutex); wait(first-delay): first-count := first-count 1; if first-count > 0 then signal(first-delay) else signal(second-delay); wait(second-delay); second-count := second-count 1; S; if first-count >0 then signal(first-delay); else if second-count >0 then signal(second-delay); else signal(mutex); Monitores Construção de sincronização de alto nível que permite o compartilhamento seguro de um tipo de dados abstrato entre processos concorrentes. type monitor-name = monitor declarações de variáveis procedure entry P1 :(); begin procedure entry P2(); begin procedure entry Pn (); begin begin código de inicialização end 6.39 6.40
Monitores (Cont.) Visão Esquemática de um Monitor Para permitir que um processo espere no monitor, uma variável condicional deve ser declarada, como var x, y: condition Variáveis condicionais somente podem ser usadas com as operações wait e signal. A operação x.wait; indica que o processo que chama esta operação está suspenso até outro processo chamar x.signal; A operação x.signal restaura exatamente um processo suspenso. Se nenhum processo está suspenso, a operação signal não possui efeito. 6.41 6.42 Monitor com Variáveis Condicionais Exemplo dos Filósofos type dining-philosophers = monitor var state : array [0..4] of :(thinking, hungry, eating); var self : array [0..4] of condition; procedure entry pickup (i: 0..4); begin state[i] := hungry, test (i); if state[i] eating then self[i], wait, procedure entry putdown (i: 0..4); begin state[i] := thinking; test (i+4 mod 5); test (i+1 mod 5); 6.43 6.44
Exemplo dos Filósofos (Cont.) procedure test(k: 0..4); begin if state[k+4 mod 5] eating and state[k] = hungry and state[k+1 mod 5] ] eating then begin state[k] := eating; self[k].signal; begin for i := 0 to 4 end. do state[i] := thinking; Implementação de Monitor com Semáforos Variáveis var mutex: semaphore (init = 1) next: semaphore (init = 0) next-count: integer (init = 0) Cada procedimento externo F será substituído por corpo de F; if next-count > 0 then signal(next) else signal(mutex); Exclusão mútua dentro do monitor é garantida. 6.45 6.46 Implementação de Monitor (Cont.) Implementação de Monitor (Cont.) Para cada variável condicional x, tem-se: var x-sem: semaphore (init = 0) x-count: integer (init = 0) A operação x.wait pode ser implementada como: x-count := x-count + 1; if next-count >0 then signal(next) else signal(mutex); wait(x-sem); x-count := x-count 1; A operação x.signal pode ser implementada como: if x-count > 0 then begin next-count := next-count + 1; signal(x-sem); wait(next); next-count := next-count 1; 6.47 6.48
Implementação de Monitor (Cont.) Sistema Operacional Solaris 2 Construção da espera condicional: x.wait(c); c expressão inteira avaliada quando a operação wait é executada. Valor de c (número de prioridade) é armazenado com o nome do processo que está suspenso. Quando x.signal é executado, o processo com o menor número de prioridade associada é executado. Verificar duas condições para estabelecer o correto funcionamento do sistema: Processos usuários devem sempre realizar suas chamadas em uma seqüência correta. Deve garantir que um processo não cooperativo não ignore o protocolo de acesso ao recurso pelo monitor e tente usar diretamente o recurso compartilhado. Implementa uma variedade de mecanismos de sincronização para suportar multitarefa, múltiplos fluxos de execução (incluindo threads em tempo real), e multiprocessamento. Usa semáforos adaptáveis para eficiência quando protegendo dados de pequenos segmentos de códigos. Usa variáveis condicionais e semáforos leitores e escritores quando seções maiores de código necessitam acessar os dados.. 6.49 6.50