• Nenhum resultado encontrado

Esses exemplos ilustram que vários tipos de erros podem ser gerados facilmente quando os programado res utilizam semáforos de forma incorreta para resolver o problema de seção crítica Problemas semelhantes

No documento [PT] SILBERSCHATZ - Sistemas Operacionais (páginas 162-165)

142 • Sistemas Operacionais

Para lidar com tais erros, pesquisadores desenvolveram estruturas cm linguagem de alto nível. Nesta sc- ção, vamos descrever uma estrutura de sincronização de alto nível - o tipo monitor.

Lembre-se que um típo, ou tipo de dados abstrato, encapsula dados privados com métodos públicos para realizar operações com os dados. Um monitor apresenta uma série de operações definidas pelo programador que recebem exclusão mútua no monitor. O tipo monitor também contêm a declaração de variáveis cujos va- lores definem o estado de uma instância desse tipo, juntamente com o corpo dos procedimentos ou funções que operam essas variáveis. O pseudocódigo semelhante a Java que descreve a sintaxe de um monitor é:

monitor nome-do-monitor { //declarações de v a r i á v e l p u b t i c e n t r y p l ( . . . ) { • * * } public entry p2(...){ * - - }

í

A implementação interna de um tipo monitor não pode ser acessada diretamente pelos vários threads. Um procedimento definido em um monitor só poderá acessar as variáveis que estiverem declaradas local- mente no monitor e qualquer parâmetro formal que for passado para os procedimentos. O encapsulamento fornecido pelo tipo monitor também limita o acesso às variáveis locais apenas pelos procedimentos locais.

A estrutura do monitor proíbe o acesso concorrente a todos os procedimentos definidos no monitor. Portanto, apenas um thread (ou processo) de cada vez pode estar ativo no monitor em determinado momen- to. Consequentemente, o programador não precisa codificar essa sincronização explicitamente; ela está in- corporada no tipo monitor.

Variáveis do tipo condi tion desempenham um papel especial nos monitores, por conta de operações es- peciais que podem ser chamadas sobre elas: wai t e si gnal. Um programador que precise escrever seu próprio esquema de sincronização personalizado pode definir uma ou mais variáveis do tipo condition:

condition x , y ;

A operação x.wai t; significa q u e o thread que chama essa operação ficará suspenso ate" que outro thread chame

x . s i g n a l ;

A operação x.signal retoma exatamente um thread. Se nenhum thread estiver suspenso, a operação sig- na J não tem efeito; ou seja, o estado de x é como se a operação nunca tivesse ocorrido (Figura 7.26). Compare esse esquema com a operação V com semáforos, que sempre afota o estado do semáforo.

Agora vamos supor que, quando a operação x.signal for chamada por um thread P, exista um thread Q suspenso associado com a condição x. Claramente, se o thread Q suspenso pode retomar sua execução, o thread P de sinalização deve esperar. Caso contrário, P e Q ficarão ativos ao mesmo tempo no monitor. Observe, no entanto, que os dois threads podem conccitualmcnte continuar com sua execução. Existem duas possibilidades:

1. Signal-und-Waií (sinalizar e esperar) - Pespera até Q sair do monitor, ou espera por outra condição. 2. Signal-and-Conti/iue (sinalizar e continuar) - Q espera até P sair do monitor, ou espera por outra

condição.

Existem argumentos razoáveis em favor da adoção das duas opções. Como P já estava executando no mo- nitor, Signal-and-Continue parece mais razoável. No entanto, se deixarmos P continuar, então, quando Q ti- ver sido retomado, a condição lógica pela qual Q estava esperando pode não ser mais válida. Signai-and-Wait foi defendido por Hoare, principalmente porque o argumento anterior a seu favor se traduz diretamente em

Sincronização de Processos • 143

(Ha de entradas ^ V ^ Y - ^

f comparlllhados \ . ^ - - - " ^ ^

filas associadas com f /x --{3*GÍ*Q< \ as condições x. y \ / y - * G * Q . \

\ operações /

\ código d e y ' X ^ i n i c i a i i z a ç ã o ^ X

Figura 7.26 Monitor com variáveis de condição.

regras de prova simples e elegantes. Uni meio-termo entre essas duas opções foi adotado na linguagem Con- current Pascal (Pascal Concorrente). Q u a n d o o thread P executa a operação signal, ele imediatamente sai do monitor. Portanto, Q é imediatamente retomado. Esse modelo c menos poderoso do que o modelo de Hoa- rc, porque um thread não pode sinalizar mais de uma ve*/. durante unia única chamada de procedimento. Vamos ilustrar esses conceitos apresentando uma solução livre de deadlocks para o problema do jantar dos filósofos. Lembre-se de q u e um filósofo pode pegar seus pauzinhos apenas se ambos estiverem disponí- veis. Para codificar essa solução, ê preciso fazer a distinção entre três estados nos quais o filósofo pode se en- contrar. Para isso, apresentamos as seguintes estruturas de dados:

i n t [ ] state - new i n t [ 5 ] ;

s t a t i c f i n a l t n t THINKING • 0; s t a t i c f i n a l i n t HUNGRY • 1; s t a t i c f i n a l i n t EATlNG = 2;

O filósofo; só pode definir a variável s t a t e [ i ] = EATING se seus dois vizinhos n ã o estiverem comendo, ou seja, a condição ( s t a t e [ ( i * 4) % 5] !=EATING) and ( s t a t e [ ( i + 1) % 5] I=EATING) é verdadeira.

Também precisamos declarar

condit1on[ ] s e l f = new c o n d t t i o n [ 5 ] ;

onde o filósofo / pode se atrasar quando estiver com fome, mas não puder obter os pauzinhos necessários. Agora estamos em condições de descrever nossa solução para o problema do jantar dos filósofos. A distri- buição dos pauzinhos é controlada pelo monitor dp, que é uma instância do tipo monitor dini ngPhi 1 osophers, cuja definição usando um pseudocódigo semelhante à Java é apresentada na Figura 7.27. Cada filósofo, antes de começar a comer, deve chamar a operação pi ckUp( ). Isso poderá resultar na suspensão do thread do filó- sofo. Após a conclusão bem-sucedida da operação, o filósofo pode comer. Depois de comer, o filósofo chama a operação putDown( ), e começa a pensar. Assim, o filósofo / deve chamar as operações pickUpt ) e put- Down( ) na seguinte sequência:

dp.piCkUp(i); eat( ) ; dp.putDQwn(i);

144 • Sistemas Operacionais

Ê fácil mostrar que essa solução garante q u e dois filósofos vizinhos não estarão comendo ao mesmo tem- po t que não haverá deadlocks. Observamos, no entanto, que é possível que um filósofo morra de fome. Não vamos apresentar uma solução para esse problema, pedindo, em vez disso, que você desenvolva uma na seção de exercícios mais adiante.

monitor diningPhilosophers { i n t [ ] state = new i n t [ 5 ] ; s t a t i c f i n a l i n t THINKING - 0; s t a t i c fina» i n t HUNGRY = 1; s t a t i c f i n a l i n t EATING • 2;

condition[ ] self • new c o n d i t i o n [ 5 ] ;

public diningPhilosophers ( f o r ( i n t i = 0 ; i < 5 ; i++)

s t a t e [ i ] • THINKING;

I

public entry pickUp(ínt 0 ( s t a t e f i ] » HUNGRY;

t e s t ( i ) ;

if ( s t a t e f i ] ! • EATING) s e l f [ i ] . w a i t ;

I

public entry putOown(int i) { state[1] = THINKING;

// testar vizinhos à esquerda e à d i r e i t a t e s t ( ( i • 4) % 5 ) ; test((i + 1) % 5); I private t e s t ( i n t i) I if { ( s t a t e [ { i + 4) % S] != EATING) && ( s t a t e f i ] " HUNGRY) S& ( s t a t e [ ( i • 1) * 5] !* EATING) ) { s t a t e [ i ) = EATING; s e l f [ f ] . s i g n a l ; 1 ) \

Figura 7.27 Uma solução com monitor para o problema do jantar dos filósofos.

No documento [PT] SILBERSCHATZ - Sistemas Operacionais (páginas 162-165)

Outline

Documentos relacionados