Problemas Clássicos de Sincronização Filósofos Jantando Filósofo i: Solução Óbvia O problema dos filósofos jantando foi introduzido e solucionado por Dijkstra em 1965. Neste problema, 5 filósofos estão sentados ao redor de uma mesa redonda e cada filósofo tem um prato de espaguete. Entre cada prato há um garfo e o espaguete está tão escorregadio que o filósofo precisa de 2 garfos para comê-lo. A vida do filósofo consiste em períodos alternados de comer e de pensar. Quando o filósofo fica com fome, ele tenta pegar o garfo da direita e o da esquerda, um de cada vez, em qualquer ordem. ausência de deadlock Boas soluções ausência de starvation alto grau de paralelismo 5 filósofos com fome, 2 podem comer Uma boa solução para o problema dos filósofos jantando é não permitir que ocorra deadlock, ou seja, quando todos os filósofos param esperando que alguém libere o garfo. Outra situação que devemos evitar é starvation onde todos os filósofos tentam continuar a executar a tentativa de pegar os garfos, mas não há nenhum progresso, ou seja, todos passam fome. Além disso, se temos 5 filósofos com fome e temos 5 garfos seria interessante deixar 2 filósofos comer obtendo assim um alto grau de paralelismo. O procedimento wait(garfo(i)) espera até que o garfo especificado esteja disponível e então, pega-o. O garfo i é o garfo da esquerda e o garfo i+1 é o garfo da direita. Esta solução não está correta pois se os 5 filósofos pegarem seus garfos esquerdos simultaneamente, nenhum será capaz de pegar o garfo direito e haverá um deadlock. O programa óbvio poderia ser modificado da seguinte maneira. O filósofo, após pegar o garfo esquerdo verifica se o garfo direito está disponível. Se não estiver disponível, ele devolve o garfo esquerdo e espera por um tempo e então repete o procedimento. Esta solução não funciona se os filósofos tentam pegar os garfos esquerdos simultaneamente. Então todos vão devolver os garfos e esperar por um tempo. Se o tempo de espera é igual para todos, então todos vão tentar pegar o garfo simultaneamente e assim por diante, para sempre. Solução com lock Filósofo i: Um aprimoramento que não resulta em deadlock nem em fome é proteger os procedimentos com um semáforo binário (lock). Antes de começar a pegar um dos garfos, o filósofo faria um wait no semáforo e após comer e devolver os garfos, faria um signal no semáforo. Neste caso, apenas um filósofo por vez pode estar comendo. Profa. Tiemi Christine Sakata 1
Solução Assimétrica if (i % 2 == 0) else Solução do livro Tanenbaum semaforo lock; semaforo filosofo[n] = {0, 0, 0,..., 0} int estado[n] = {T, T, T,...,T} pega_garfos(); solta_garfos(); pega_garfos() estado[i] = H; testa_garfos(i); wait(filosofo[i]); Suponha que todos os filósofos estão com fome. Cenário com paralelismo: F 0 está comendo F 2 pega garfos 2 e 3. F 1 não pega garfo nenhum. Cenário em que não há paralelismo: F 0 está comendo F 1 pega o garfo 2 F 4 pega o garfo 4 F 2 não consegue pegar o garfo 2 testa_garfos(int i) if (estado[i] == H && estado[fil_esq]!= E && estado[fil_dir]!= E) estado[i] = E; signal(filosofo[i]); solta_garfos() estado[i] = T; testa_garfos(fil_esq); testa_garfos(fil_dir); Nesta solução, temos que cada filósofo pode estar no estado T (thinking), E (eating) ou H (hungry). Um filósofo só pode passar para o estado E se nenhum vizinho estiver comendo. Os vizinhos do filósofo i são definidos pelas macros fil esq e fil dir. F 3 não consegue pegar o garfo 4 Profa. Tiemi Christine Sakata 2
Leitores e Escritores Dois tipos de processos: leitores e escritores Leitores: apenas lêem as informações Escritores: alteram os dados Exemplo: reserva de passagem aérea Um exemplo do conceito de leitores e escritores é um sistema de reserva de passagens aéreas. Os leitores são aqueles que precisam de informação de vôo. Note que o sistema pode permitir que vários leitores estejam ativos em um determinado tempo pois a base de dados não é alterada por ninguém. Os escritores são aqueles que desejam reservar um lugar em um determinado vôo. O sistema não deve permitir que alguém escreva ou leia enquanto um um escritor está ativo. Portanto, é preciso implementar uma poĺıtica de exclusão mútua sempre que houver um grupo de escritores e leitores. Nesta solução, o primeiro leitor que recebe acesso ao banco de dados faz um wait(db) impedindo de um escritor escrever. Os leitores subseqüentes incrementam um contador numl e ao terminar a leitura, esse contador é decrementado. Quando todos os leitores terminam sua execução, o semáforo db é liberado ao escritor. Nesta solução, se enquanto um leitor está usando o banco de dados, aparece um outro leitor, o segundo leitor é admitido. Se um escritor aparece, mas se existe leitores ativos constantemente, o escritor nunca será desbloqueado. Outra solução requer que o escritor, assim que estiver pronto, faça sua escrita o mais rápido possível, ou seja, se um escritor estiver esperando para acessar a RC, nenhum outro leitor poderá iniciar a leitura. Neste caso, os leitores podem sofrer paralisação. Houre (1974) propôs uma outra solução para este problema. Assim que um escritor termina, todos os leitores em espera recebem permissão para execução. Em seguida, assim que o grupo de leitores termina, o escritor que estava esperando pode começar e assim sucessivamente. Solução de Courtois leitor: while(true) numl = numl + 1; if (numl == 1) wait(db); le_db(); numl = numl - 1; if (numl == 0) signal(db); escritor: while(true) wait(db); escreve_db(); signal(db); Solução de Courtois Profa. Tiemi Christine Sakata 3
Barbeiro Adormecido O semáforo clientes conta os clientes que esperam, barbeiro indica se o barbeiro está ocupado e lock é utilizado para exclusão mútua. Quando o barbeiro chega para trabalhar, ele fica dormindo até algum cliente chegar. Quando um cliente chega, ele acorda o barbeiro. Se outro cliente chega logo em seguida, o segundo cliente será bloqueado até que o primeiro cliente libere o lock. Quando o corte de cabelo termina, o cliente sai da barbearia. O barbeiro entretanto, tenta receber o próximo cliente e na ausência de clientes, ele volta a dormir. O problema do barbeiro adormecido acontece em uma barbearia. A barbearia tem um barbeiro, uma cadeira de barbeiro e n cadeiras para os clientes esperarem. Quando um cliente chega, ele tem que acordar o barbeiro. Se um cliente chega e o barbeiro está trabalhando, ele senta se houver cadeira vazia ou vai embora se todas as cadeiras estiverem ocupadas. Solução 1 semaforo lock = 1, barbeiro = 0, clientes = 0; int cesperando = 0; cesperando = cesperando + 1; signal (lock); Solução 2 semaforo cadeiras = 5; semaforo barbeiro = 0; semaforo clientes = 0; semaforo cabelo_cortado = 0; signal(cabelo_cortado); Solução 2 if (trywait(cadeiras) == 0) wait(cabelo_cortado); signal(cadeiras); Solução 1 if (cesperando < CADEIRAS) cesperando = cesperando + 1; cabelo_cortado(); else O comando trywait tenta executar um wait no semáforo cadeira, ou seja, tenta decrementar o valor de cadeira. Se fosse executado apenas um wait, formaria uma fila dos clientes que não conseguem sentar na cadeira. Nesta solução, adicionamos um semáforo para sincronizar o corte do cabelo e outro para limitar o número de clientes esperando na cadeira. Profa. Tiemi Christine Sakata 4
Solução 3 semaforo cadeiras = 5; semaforo barbeiro = 0; semaforo clientes = 0; semaforo cabelo_cortado = 0; semaforo levantou = 0; signal(cabelo_cortado); wait(levantou); Solução 3 if (trywait(cadeiras) == 0) wait(cabelo_cortado); signal(levantou); signal(cadeiras); Nesta solução, um semáforo levantou foi adicionado para que o barbeiro saiba que o cliente levantou e deixar outro cliente sentar. Profa. Tiemi Christine Sakata 5