• Nenhum resultado encontrado

7.9 • Sincronização de sistemas operacionais

No documento [PT] SILBERSCHATZ - Sistemas Operacionais (páginas 176-181)

A seguir descrevemos os mecanismos de sincronização fornecidos pelos sistemas operacionais Solaris e Win- dows NT.

7.9.1 Sincronização no Solaris 2

O Solaris 2 foi projetado para suportar a computação de tempo real, multithreading e múltiplos processado- res. Para controlar o acesso a seções críticas, o Solaris 2 fornece mutex adaptativos, variáveis de condição, se- máforos e blocos de operações de leitura e escrita.

Um mutex adaptativo protege o acesso a cada item de dados crítico. Em um sistema multiprocessador, um mutex adaptativo começa comi) um semáforo padrão implementado como um spinhck. Sc os dados es- tiverem bloqueados e, portanto, já cm uso, o mutex adaptativo tem duas ações possíveis. Se o bloco de ope- rações for mantido por um thread que está em execução no momento em outra CPU, o thread gira em espe- ra ocupada enquanto estiver esperando que o bloco de operações fique disponível, porque o thread que está mantendo o bloco de operações deverá estar quase concluído. Se o thread que estiver mantendo o blo- co de operações não estiver no estado de execução no momento, o thread hloqueará, sendo suspenso até ser ativado pela liberação do bloco de operações. Ele é suspenso para que evite a espera ocupada quando o bloco de operações não puder ser liberado de modo relativamente rápido. Um bloco de operações mantido por um thread suspenso tende a estar nessa categoria. Em um sistema uniprocessador, o thread que detém o bloco de operações nunca estará executando se o bloco de operações estiver sendo testado por outro thread, porque somente um thread pode executar de cada vez. Portanto, em um sistema uniprocessador, os thre- ads sempre ficam em estado suspenso em vez de girar se encontrarem um bloco de operações. Usamos o método de mutex adaptativo para proteger apenas os dados que são acessados por segmentos curtos de có- digo. Ou seja, um mutex é usado se um bloco de operações for mantido por menos do que algumas centenas de instruções. Se o segmento de código for maior do que isso, a espera com giro será muito ineficiente. Para segmentos de código mais longos, serão utilizados variáveis de condição e semáforos. Se o bloco de opera- ções desejado já estiver sendo mantido, o thread chama uma operação de espera e é suspenso. Quando um thread libera o bloco de operações, ele emite um sinal para o próximo thread suspenso na fila. O custo ex- tra de suspender e tornar a ativar um thread e das trocas de contexto associadas é menor do que o custo de desperdiçar várias centenas de instruções esperando em um spinlock.

Observe que os mecanismos de bloco de operações usados pelo kernel também são implementados para threads de usuário, por isso os mesmos tipos de blocos de operações estão disponíveis dentro e fora do ker- nel. Uma diferença crucial de implementação ocorre na área de herança de prioridades. As rotinas de bloco de operações do kernel seguem os métodos de herança de prioridades do kernel utilizados pelo escalonador, conforme descrito na Seção 6.5. Os mecanismos de bloco de operações de threads usuário não fornecem essa funcionalidade, pois é específica do kernel.

Os blocos de operações de leitura e escrita são usados para proteger dados que são acessados com fre- quência, mas geralmente apenas como somente leitura. Nessas circunstâncias, os blocos de operações de leitura e escrita são mais eficientes do que os semáforos, porque múltiplos threads podem estar lendo da- dos de forma concorrente, enquanto os semáforos sempre iriam serializar o acesso aos dados. Os blocos de operações de leitura e escrita têm uma implementação relativamente cara, por isso são utilizados ape- nas cm segmentos longos de código.

Para otimizar o desempenho do Solaris, os desenvolvedores ajustaram e refinaram os métodos de bloco de operações. Como os blocos de operações são usados com frequência c geralmente são utilizados para funções críticas de kernel. grandes ganhe >s de desempenho podem ser alcançados ajustando sua implementação e uso.

156 • Sistemas Operacionais

7 . 9 . 2 S i n c r o n i z a ç ã o n o W i n d o w s N T

0 Windows NT é um kernel com múltiplos threads que também fornece suporte para aplicações de tempo real e múltiplos processadores. O NT fornece vários tipos de objetos de sincronização para lidar com ex- clusão mútua, incluindo mutexes, scções críticas, semáforos e objetos de evento. Os dados compartilhados são protegidos exigindo que um thrcad obtenha a posse de um mutex para acessar os dados e libere a posse quando tiver terminado. Um objeto de seção crítica tem um comportamento semelhante a um mutex, exce- to pelo fato de que ele somente pode ser usado para sincronização entre aqueles threads que pertencerem ao mesmo processo. Os eventos são objetos de sincronização que podem ser utilizados como as variáveis de condição, ou seja, eles podem notificar um thrcad em espera quando uma condição desejada ocorrer.

7.10 • Resumo

Dado um conjunto de processos ou threads sequenciais cooperativos que compartilham dados, a exclusão mútua deve ser fornecida para evitar a ocorrência de uma condição de corrida em relação aos dados compar- tilhados. Uma solução é garantir que uma seção crítica de código esteja sendo usada apenas por um processo ou thrcad de cada vez. Existem diferentes soluções de software para resolver o problema de seção crítica, par- tindo do pressuposto de que apenas o bloco de operações de memória está disponível.

A principal desvantagem das soluções de software é que não são fáceis de generalizar para múltiplos threads ou para problemas mais complexos do que a seção crítica. Os semáforos superam essa dificuldade. Podemos usar semáforos para resolver vários problemas de sincronização e podemos implementá-los de for- ma eficiente, especialmente se houver suporte de hardware disponível para operações atómicas.

Vários problemas de sincronização distintas (tais como buffer limitado, dos leitores-cscritorcs e do jantar dos filósofos) são importantes, basicamente porque são exemplos de uma vasta classe de problemas de con- trole de concorrência. Esses problemas são usados para testar praticamente todos os novos esquemas de sin- cronização propostos.

Java fornece um mecanismo para coordenar as atividades de múltiplos threads quando estiverem aces- sando dados compartilhados, através das instruções synchronized, wait( ), notifyí )e notifyAll ( ). A sincronização Java é fornecida no nível da linguagem, sendo considerada um exemplo de um mecanismo de sincronização de nível mais alto chamado monitor. Além de Java, muitas linguagens forneceram suporte a monitores, incluindo Concurrent Pascal c Mesa.

Os sistemas operacionais também suportam a sincronização de threads. Por exemplo, 0 Solaris 2 e o Windows NT fornecem mecanismos tais como semáforos, mutexes c variáveis de condição para controlar o acesso a dados compartilhados.

• Exercícios

7.1 A primeira solução de software correta conhecida para o problema de seção crítica para dois threads foi desenvolvida por Dekker; ela está apresentada na Figura 7.42. Os dois threads, T0 c 7";, coorde- nam a atividade compartilhando um objeto da classe Dekker. Mostre que o algoritmo satisfaz a todos os três requisitos do problema de seção crítica.

7.2 No Capítulo 5, demos uma solução de multithreading ao problema do buffer limitado que utilizava a troca de mensagens. A classe MessageQueue não é considerada segura para threads, o que significa que uma condição de corrida é possível quando múltiplos threads tentarem acessar a fila de forma concor- rente. Modifique a classe MessageQueue utilizando a sincronização Java para que seja segura.

7.3 Crie uma classe BinarySemaphore que implemente um semáforo binário.

7.4 A instrução wait( ) em todos os exemplos de programa Java era parte do laço wh i le. Explique porque é preciso sempre utilizar uma instrução while quando estiver usando wait( ) e por que a instrução if nunca seria utilizada.

7.5 A solução para o problema dos leitores-escritores n ã o impede q u e os escritores em espera sofram de paralisação. Se o banco de dados estiver sendo lido no momento e houver um escritor, os leito-

Sincronização de Processos • 157 res subsequentes terão permissão para ler o banco de dados antes que um escritor possa escrever. Modifique a solução de modo a não haver paralisação dos escritores q u e estão esperando. 7.6 Uma solução baseada em monitor ao problema do jantar dos filósofos, escrito em pseudocódigo seme-

lhante a Java e utilizando variáveis de condição, foi apresentada na Seçâo 7.7. Desenvolva uma solu- ção para o mesmo problema utilizando sincronização Java.

7.7 A solução apresentada para o problema do jantar dos filósofos n ã o impede que um filósofo morra de fome. Por exemplo, dois filósofos, digamos, o filósofo, e o filósofo^, poderiam alternar entre as atividades de comer e pensar, fazendo com que o filósofo^ nunca conseguisse comer. Usando a sincronização Java, desenvolva uma solução para o p r o b l e m a q u e impeça um filósofo de m o r r e r de fome.

7.8 Na Seção 7.4, mencionamos que desabilitar interrupções com frequência poderia afetar o clock do sistema. Explique o motivo para isso e como esses efeitos podem ser minimizados.

public class Dekker extends MutualExclusion ,

public Dekker( J ( flag[0] = false; flagfl] - false; turn = TURN 0; 1

public void enteringCriticalSection(int tj { (nt Other;

other » 1 - t; fUg[t) * true;

while (flag[other] •- true) ( if (turn =- other) {

flagtt] = false; while (turn »• other)

Thread.yield( ); flag[t] - true; I

)

)

public void leavingCriticalSectionfint t) ( turn • 1 - t;

flaglt] = false;

private volatile int turn;

private volatile boolean[ ] flag B new boolean[2];

I

Figura 7.42 Algoritmo de Dekker para exclusão mútua.

7.9 Neste capítulo, utilizamos a instrução synchroni zed com os métodos de instância. Chamar um método de instância requer associar o método com um objeto. Entrar no método synchroni zed exige ter apos- se do bloco de operações do objeto. Os métodos estáticos são diferentes porque não exigem associa- ção com um objeto quando são chamados. Explique como é possível declarar os métodos estáticos como synchroni zed.

7.10 O Problema de barbeiro dorminhoco. Uma barbearia consiste em uma sala de espera com n cadeiras, e um salão para o barbeiro com uma cadeira de barbear. Se não houver clientes para atender, o barbeiro vai dormir. Se entrar um cliente na barbearia c todas as cadeiras estiverem ocupadas, o cliente não fica.

158 • Sistemas Operacionais

Se o barbeiro estiver ocupado, mas houver cadeiras disponíveis, o cliente vai esperar em uma das cadei- ras livres. Se o barbeiro estiver dormindo, o cliente vai acordá-lo. Kscreva um programa para coorde- nar o barbeiro e os clientes utilizando a sincronização Java.

7.11 í > problema dos fumantes. Considere um sistema com três processos fumante e um processo agente. (iada fumante está continuamente enrolando um cigarro e fumando-o em seguida. Mas para enrolar e fumar um cigarro, o fumante precisa de três ingredientes: fumo, papel e fósforos. Um dos processos fumante tem papel, outro tem fumo e o terceiro tem os fósforos. O agente tem um suprimento infinito de todos os três materiais. O agente coloca dois dos ingredientes na mesa. O fumante que tem o outro ingrediente faz o cigarro e o fuma, sinalizando o agente na conclusão. O agente coloca outros dois dos ingredientes e o ciclo se repete. Kscreva um programa que sincronize o agente c os fumantes usando a sincronização Java.

7.12 Explique por que o Solaris 2 e o Windows NT implementam múltiplos mecanismos de bloco de ope- rações. Descreva as circunstâncias nas quais eles utilizam spinlocks. mutexes, semáforos, mutex adap- tativo e variáveis de condição. Km cada caso. explique por que o mecanismo é necessário.

Notas bibliográficas

Os algoritmos de exclusão mútua I e 2 para duas tarefas foram discutidos no trabalho clássico de Dijkstra [1965a). O algoritmo de Dekker (Exercício 7.1) - a primeira solução de software correia para o problema de exclusão mútua de dois processos - foi desenvolvido pelo matemático holandês T. Dekker. Esse algoritmo também foi discutido por Dijkstra [ 1965a]. Uma solução mais simples para o problema de exclusão mútua de dois processos foi apresentada mais tarde por Peterson [ 198 11 (algoritmo 3).

Dijkstra 11965a] apresentou a primeira solução ao problema de exclusão mútua para» processos. Essa solução, no entanto, não tem um limite superior na quantidade de tempo que determinado processo preci- sa esperar para entrar na seção crítica. Knuth [ 1966] apresentou o primeiro algoritmo com um limite; seu limite era 2" rodadas. DeBruijn 11967) refinou o algoritmo de Knuth reduzindo o tempo de espera para ir rodadas e, depois disso, Kisenberg e McGuire 11972] conseguiram reduzir o tempo para o menor limite de

n - 1 rodadas. Lamport 11974| apresentou um esquema diferente para resolver o problema de exclusão

mútua - o algoritmo do padeiro; ele também requer n - 1 rodadas, mas c mais fácil de programar c enten- der. Burns |19781 desenvolveu o algoritmo de solução de hardware que satisfaz o requisito de espera limi- tada.

Discussões gerais relativas ao problema de exclusão mútua foram apresentadas por Lamport |1986, 1991]. Uma série de algoritmos para exclusão mútua foi apresentada por Raynal [1986).

Informações sobre soluções de hardware disponíveis para a sincronização de processos podem ser en- contradas em Patterson e Hennessy 11998).

O conceito de semáforo foi sugerido por Dijkstra | I965a|. Patil [ 19711 analisou se os semáforos são ca- pazes de resolver todos os problemas de sincronização possíveis. Parnas 11975] discutiu alguns dos proble- mas nos argumentos de Patil. Kosarajul 1973) deu seguimento ao trabalho de Patil produzindo um problema que não pode ser resolvido pelas operações watt csignal. I.ipton [ 1974) discutiu a limitação das várias primi- tivas de sincronização.

Os problemas clássicos de coordenação de processos que descrevemos aqui são paradigmas de uma vasta classe de problemas de controle de concorrência. O problema do buffer limitado, o problema do jantar dos filósofos e o problema do barbeiro dorminhoco (Exercício 7.10) foram sugeridos por Dijkstra 11965a, 1971], O problema dos fumantes (Exercício 7.11) foi desenvolvido por Patil [ 19711. O problema dos leito- res-escritores foi sugerido por Courtois e colegas [ 19711. A questão de leituras e escritas concorrentes foi dis- cutida por Lamport 11977), assim como o problema de sincronização de processos independentes 11976).

O conceito de monitor foi desenvolvido por Brinch Hansen 119731. Uma descrição completa do monitor foi feita por Hoare 11974|. Kessels [ 1977) propôs uma extensão do monitor para permitir a sinalização auto- mática. Um trabalho descrevendo as classificações dos monitores foi publicado por Bulir e colegas 11995J. Discussões gerais relativas à programação concorrente foram oferecidas por Bcn-Ari [1991] e Burns e Davies [19931-

Sincronização de Processos • 159 Detalhes relativos a como Java sincroniza os threads podem ser encontrados em Oaks e W o n g 11999|, Lea (1997], e Gosling e colegas [ 1996]. Hartley (19981 faz inúmeras referencias a programação concorrente e multithreading em Java. O Java Report [ 19981 tratou dos tópicos de multithreading avançado c sincroniza- ção em Java.

As primitivas de sincronização para o W i n d o w s NT foram discutidas por Solomon [ 1998) e Pham e Garg [1996|. Detalhes dos mecanismos de blocos de operações utilizados no Solaris 2 são apresentados por Khan- na e colegas [1992], Powell e colegas [1991] e especialmente Bykholt e colegas [ 1992]. Outras referências para a sincronização do Solaris 2 incluem Vahalia [1996] e Graham [ 1995|.

Capítulo 8

DEADLOCKS

Quando vários processos competem por um número finito de recursos, poderá ocorrer o seguinte: um pro-

No documento [PT] SILBERSCHATZ - Sistemas Operacionais (páginas 176-181)

Outline

Documentos relacionados