Você está na página 1de 35

Sistemas Operacionais IV - Coordenao entre Tarefas

Prof. Carlos Alberto Maziero DAInf UTFPR http://dainf.ct.utfpr.edu.br/maziero 18 de novembro de 2011

Copyright (c) 2006 Carlos Alberto Maziero. garantida a permisso para copiar, distribuir e/ou modicar este documento sob os termos da Licena de Documentao Livre GNU (GNU Free Documentation License), Verso 1.2 ou qualquer verso posterior publicada pela Free Software Foundation. A licena est disponvel em http://www.gnu.org/licenses/gfdl.txt. Este texto foi produzido usando exclusivamente software livre: Sistema Operacional Linux (distriA buies Fedora e Ubuntu), compilador de texto L TEX 2 , gerenciador de referncias BibTeX, editor grco Inkscape, criadores de grcos GNUPlot e GraphViz e processador PS/PDF GhostScript, entre outros.

c prof. Carlos Maziero

SUMRIO 2

Sumrio
1 2 3 4 5 Objetivos Condies de disputa Sees crticas Inibio de interrupes Solues com espera ocupada 5.1 A soluo bvia . . . . . . 5.2 Alternncia de uso . . . . 5.3 O algoritmo de Peterson . 5.4 Instrues Test-and-Set . . 5.5 Problemas . . . . . . . . . Semforos Variveis de condio Monitores Problemas clssicos de coordenao 9.1 O problema dos produtores/consumidores . . . . . . . . . . . . . . . . . 9.2 O problema dos leitores/escritores . . . . . . . . . . . . . . . . . . . . . . 9.3 O jantar dos lsofos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 3 6 7 7 8 9 9 10 11 12 15 17 18 19 20 22 24 26 27 28 28 30 30

6 7 8 9

10 Impasses 10.1 Caracterizao de impasses . . . . . . . . 10.2 Grafos de alocao de recursos . . . . . . 10.3 Tcnicas de tratamento de impasses . . . 10.3.1 Preveno de impasses . . . . . . . 10.3.2 Impedimento de impasses . . . . . 10.3.3 Deteco e resoluo de impasses

c prof. Carlos Maziero

Objetivos 3

Resumo Muitas implementaes de sistemas complexos so estruturadas como vrias tarefas inter-dependentes, que cooperam entre si para atingir os objetivos da aplicao, como por exemplo em um navegador Web. Para que as vrias tarefas que compem uma aplicao possam cooperar, elas precisam comunicar informaes umas s outras e coordenar suas atividades, para garantir que os resultados obtidos sejam coerentes. Este mdulo apresenta os principais conceitos, problemas e solues referentes coordenao entre tarefas.

Objetivos

Em um sistema multi-tarefas, vrias tarefas podem executar simultaneamente, acessando recursos compartilhados como reas de memria, arquivos, conexes de rede, etc. Neste captulo sero estudados os problemas que podem ocorrer quando duas ou mais tarefas acessam os mesmos recursos de forma concorrente; tambm sero apresentadas as principais tcnicas usadas para coordenar de forma eciente os acessos das tarefas aos recursos compartilhados.

Condies de disputa

Quando duas ou mais tarefas acessam simultaneamente um recurso compartilhado, podem ocorrer problemas de consistncia dos dados ou do estado do recurso acessado. Esta seo descreve detalhadamente a origem dessas inconsistncias, atravs de um exemplo simples, mas que permite ilustrar claramente o problema. O cdigo apresentado a seguir implementa de forma simplicada a operao de depsito (funo depositar) de um valor em uma conta bancria informada como parmetro. Para facilitar a compreenso do cdigo de mquina apresentado na sequncia, todos os valores manipulados so inteiros.
1 2 3 4 5 6 7 8 9 10

typedef struct conta_t { int saldo ; // saldo atual da conta ... // outras informaes da conta } conta_t ; void depositar (conta_t* conta, int valor) { conta->saldo += valor ; }

Aps a compilao em uma plataforma Intel i386, a funo depositar assume a seguinte forma em cdigo de mquina (nos comentrios ao lado do cdigo, regi um registrador e mem(x) a posio de memria onde est armazenada a varivel x):

c prof. Carlos Maziero 00000000 <depositar>: push %ebp mov %esp,%ebp mov mov mov add mov 0x8(%ebp),%ecx 0x8(%ebp),%edx 0xc(%ebp),%eax (%edx),%eax %eax,(%ecx)

Condies de disputa 4

# guarda o valor do "stack frame" # ajusta o stack frame para executar a funo # # # # # mem(saldo) mem(saldo) mem(valor) [reg1 , reg2 ] [reg1 , reg2 ] reg1 reg2 reg3 + reg3 [reg1 , reg2 ] mem(saldo)

leave ret

# restaura o stack frame anterior # retorna funo anterior

Considere que a funo depositar faz parte de um sistema mais amplo de controle de contas bancrias, que pode ser acessado simultaneamente por centenas ou milhares de usurios em terminais distintos. Caso dois clientes em terminais diferentes tentem depositar valores na mesma conta ao mesmo tempo, existiro duas tarefas acessando os dados (variveis) da conta de forma concorrente. A gura 1 ilustra esse cenrio.
aplicao
depositar R$50 tarefa 1 depositar depositar tarefa 2 depositar R$1000

terminal 1

conta saldo inicial R$0

terminal 2

Figura 1: Acessos concorrentes a variveis compartilhadas. O comportamento dinmico da aplicao pode ser modelado atravs de diagramas de tempo. Caso o depsito da tarefa t1 execute integralmente antes ou depois do depsito efetuado por t2 , teremos os diagramas de tempo da gura 2. Em ambas as execues o saldo inicial da conta passou de R$ 0,00 para R$ 1050,00, conforme esperado. No entanto, caso as operaes de depsito de t1 e de t2 se entrelacem, podem ocorrer interferncias entre ambas, levando a resultados incorretos. Em sistemas monoprocessados, a sobreposio pode acontecer caso ocorram trocas de contexto durante a execuo do depsito. Em sistemas multi-processados a situao ainda mais complexa, pois cada tarefa poder estar executando em um processador distinto. Os diagramas de tempo apresentados na gura 3 mostram execues onde houve entrelaamento das operaes de depsito de t1 e de t2 . Em ambas as execues o saldo nal no corresponde ao resultado esperado, pois um dos depsitos perdido. No caso, apenas concretizado o depsito da tarefa que realizou a operao mem(saldo) reg1 por ltimo1 . Os erros e inconsistncias gerados por acessos concorrentes a dados compartilhados, como os ilustrados na gura 3, so denominados condies de disputa, ou condies de corrida (do ingls race conditions). Condies de disputa podem ocorrer em qualquer
No h problema em ambas as tarefas usarem os mesmos registradores reg1 e reg2 , pois os valores de todos os registradores so salvos/restaurados a cada troca de contexto entre tarefas.
1

c prof. Carlos Maziero


t1
saldo: R$ 0 reg1 = mem(saldo) reg2 = mem(valor) reg1 = reg1 + reg2 mem(saldo) = reg1 saldo: R$ 50 reg1 = mem(saldo) reg2 = mem(valor) reg1 = reg1 + reg2 mem(saldo) = reg1 saldo: R$ 1050
t t t

Condies de disputa 5
t2 t1
saldo: R$ 0 reg1 = mem(saldo) reg2 = mem(valor) reg1 = reg1 + reg2 mem(saldo) = reg1 saldo: R$ 1000 reg1 = mem(saldo) reg2 = mem(valor) reg1 = reg1 + reg2 mem(saldo) = reg1 saldo: R$ 1050
t

t2

Figura 2: Operaes de depsitos no-concorrentes.


t1
saldo: R$ 0 reg1 = mem(saldo) reg1 = mem(saldo) reg2 = mem(valor) reg2 = mem(valor) reg1 = reg1 + reg2 reg1 = reg1 + reg2 mem(saldo) = reg1 saldo: R$ 50 mem(saldo) = reg1 saldo: R$ 1000
t t t

t2

t1
saldo: R$ 0 reg1 = mem(saldo) reg1 = mem(saldo) reg2 = mem(valor) reg2 = mem(valor) reg1 = reg1 + reg2 reg1 = reg1 + reg2 mem(saldo) = reg1 saldo: R$ 1000 mem(saldo) = reg1 saldo: R$ 50

t2

Figura 3: Operaes de depsito concorrentes. sistema onde vrias tarefas (processos ou threads) acessam de forma concorrente recursos compartilhados (variveis, reas de memria, arquivos abertos, etc). Finalmente, condies de disputa somente existem caso ao menos uma das operaes envolvidas seja de escrita; acessos de leitura concorrentes entre si no geram condies de disputa. importante observar que condies de disputa so erros dinmicos, ou seja, que no aparecem no cdigo fonte e que s se manifestam durante a execuo, sendo dicilmente detectveis atravs da anlise do cdigo fonte. Alm disso, erros dessa natureza no se manifestam a cada execuo, mas apenas quando certos entrelaamentos ocorrerem. Assim, uma condio de disputa poder permanecer latente no cdigo durante anos, ou mesmo nunca se manifestar. A depurao de programas contendo condies de disputa pode ser muito complexa, pois o problema s se manifesta com acessos simultneos aos mesmos dados, o que pode ocorrer raramente e ser difcil de reproduzir durante a depurao. Por isso, importante conhecer tcnicas que previnam a ocorrncia de condies de disputa.

c prof. Carlos Maziero

Sees crticas 6

Sees crticas

Na seo anterior vimos que tarefas acessando dados compartilhados de forma concorrente podem ocasionar condies de disputa. Os trechos de cdigo de cada tarefa que acessam dados compartilhados so denominados sees crticas (ou regies crticas). No caso da gura 1, as sees crticas das tarefas t1 e t2 so idnticas e resumidas seguinte linha de cdigo:
1

conta.saldo += valor ;

De modo geral, sees crticas so todos os trechos de cdigo que manipulam dados compartilhados onde podem ocorrer condies de disputa. Um programa pode ter vrias sees crticas, relacionadas entre si ou no (caso manipulem dados compartilhados distintos). Para assegurar a correo de uma implementao, deve-se impedir o entrelaamento de sees crticas: apenas uma tarefa pode estar na seo crtica a cada instante. Essa propriedade conhecida como excluso mtua. Diversos mecanismos podem ser denidos para impedir o entrelaamento de sees crticas e assim prover a excluso mtua. Todos eles exigem que o programador dena os limites (incio e o nal) de cada seo crtica. Genericamente, cada seo crtica i pode ser associada a um identicador csi e so denidas as primitivas enter(ta , csi ), para que a tarefa ta indique que deseja entrar na seo crtica csi , e leave(ta , csi ), para que ta informe que est saindo da seo crtica csi . A primitiva enter(csi ) bloqueante: caso uma tarefa j esteja ocupando a seo crtica csi , as demais tarefas que tentarem entrar devero aguardar at que a primeira libere a seo crtica, atravs da primitiva leave(csi ). Usando as primitivas enter e leave, o cdigo da operao de depsito visto na Seo 2 pode ser reescrito como segue:
1 2 3 4 5 6 7 8 9 10 11 12 13

typedef struct conta_t { int saldo ; int numero ; ... } conta_t ; void depositar (conta_t* { enter (conta->numero) conta->saldo += valor leave (conta->numero) }

// saldo atual da conta // identificao da conta (seo crtica) // outras informaes da conta

conta, int valor) ; // tenta entrar na seo crtica ; // est na seo crtica ; // sai da seo crtica

Nas prximas sees sero estudadas vrias solues para a implementao das primitivas enter e leave, bem como abordagens alternativas. As solues propostas devem atender a alguns critrios bsicos que so enumerados a seguir: Excluso mtua : somente uma tarefa pode estar dentro da seo crtica em cada instante. Espera limitada : uma tarefa que aguarda acesso a uma seo crtica deve ter esse acesso garantido em um tempo nito.

c prof. Carlos Maziero

Inibio de interrupes 7

Independncia de outras tarefas : a deciso sobre o uso de uma seo crtica deve depender somente das tarefas que esto tentando entrar na mesma. Outras tarefas do sistema, que no momento no estejam interessadas em entrar na regio crtica, no podem ter inuncia sobre essa deciso. Independncia de fatores fsicos : a soluo deve ser puramente lgica e no depender da velocidade de execuo das tarefas, de temporizaes, do nmero de processadores no sistema ou de outros fatores fsicos.

Inibio de interrupes

Uma soluo simples para a implementao das primitivas enter e leave consiste em impedir as trocas de contexto dentro da seo crtica. Ao entrar em uma seo crtica, a tarefa desativa (mascara) as interrupes que possam provocar trocas de contexto, e as reativa ao sair da seo crtica. Apesar de simples, essa soluo raramente usada para a construo de aplicaes devido a vrios problemas: Ao desligar as interrupes, a preempo por tempo ou por recursos deixa de funcionar; caso a tarefa entre em um lao innito dentro da seo crtica, o sistema inteiro ser bloqueado. Uma tarefa mal-intencionada pode forar essa situao e travar o sistema. Enquanto as interrupes esto desativadas, os dispositivos de entrada/sada deixam de ser atendidos pelo ncleo, o que pode causar perdas de dados ou outros problemas. Por exemplo, uma placa de rede pode perder novos pacotes se seus buers estiverem cheios e no forem tratados pelo ncleo em tempo hbil. A tarefa que est na seo crtica no pode realizar operaes de entrada/sada, pois os dispositivos no iro responder. Esta soluo s funciona em sistemas mono-processados; em uma mquina multi-processada ou multi-core, duas tarefas concorrentes podem executar simultaneamente em processadores separados, acessando a seo crtica ao mesmo tempo. Devido a esses problemas, a inibio de interrupes uma operao privilegiada e somente utilizada em algumas sees crticas dentro do ncleo do sistema operacional e nunca pelas aplicaes.

Solues com espera ocupada

Uma primeira classe de solues para o problema da excluso mtua no acesso a sees crticas consiste em testar continuamente uma condio que indica se a seo desejada est livre ou ocupada. Esta seo apresenta algumas solues clssicas usando essa abordagem.

c prof. Carlos Maziero

A soluo bvia 8

5.1

A soluo bvia

Uma soluo aparentemente trivial para o problema da seo crtica consiste em usar uma varivel busy para indicar se a seo crtica desejada est livre ou ocupada. Usando essa abordagem, a implementao das primitivas enter e leave poderia ser escrita assim:
1 2 3 4 5 6 7 8 9 10 11 12

int busy = 0 ;

// a seo est inicialmente livre

void enter (int task) { while (busy) ; // espera enquanto a seo estiver ocupada busy = 1 ; // marca a seo como ocupada } void leave (int task) { busy = 0 ; // libera a seo (marca como livre) }

Infelizmente, essa soluo bvia e simples no funciona! Seu grande defeito que o teste da varivel busy (na linha 5) e sua atribuio (na linha 6) so feitos em momentos distintos; caso ocorra uma troca de contexto entre as linhas 5 e 6 do cdigo, poder ocorrer uma condio de disputa envolvendo a varivel busy, que ter como consequncia a violao da excluso mtua: duas ou mais tarefas podero entrar simultaneamente na seo crtica (vide o diagrama de tempo da gura 4). Em outras palavras, as linhas 5 e 6 da implementao tambm formam uma seo crtica, que deve ser protegida.

t1
busy: 0 while(busy) { }; busy: 0 busy = 1 busy: 1

t2

while(busy) { };

busy = 1
acesso seo crtica

busy: 1

violao da excluso mtua !


t t

acesso seo crtica

Figura 4: Condio de disputa no acesso varivel busy.

c prof. Carlos Maziero

Alternncia de uso 9

5.2

Alternncia de uso

Outra soluo simples para a implementao das primitivas enter e leave consiste em denir uma varivel turno, que indica de quem a vez de entrar na seo crtica. Essa varivel deve ser ajustada cada vez que uma tarefa sai da seo crtica, para indicar a prxima tarefa a us-la. A implementao das duas primitivas ca assim:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

int turn = 0 ; int num_tasks ; void enter (int task) { while (turn != task) ; } // task vale 0, 1, ..., num_tasks-1 // a tarefa espera seu turno

void leave (int task) { if (turn < num_tasks-1) // o turno da prxima tarefa turn ++ ; else turn = 0 ; // volta primeira tarefa }

Nessa soluo, cada tarefa aguarda seu turno de usar a seo crtica, em uma sequncia circular: t0 t1 t2 tn1 t0 . Essa abordagem garante a excluso mtua entre as tarefas e independe de fatores externos, mas no atende os demais critrios: caso uma tarefa ti no deseje usar a seo crtica, todas as tarefas t j com j > i caro impedidas de faz-lo, pois a varivel turno no ir evoluir.

5.3

O algoritmo de Peterson

Uma soluo correta para a excluso mtua no acesso a uma seo crtica por duas tarefas foi proposta inicialmente por Dekker em 1965. Em 1981, Gary Peterson props uma soluo mais simples e elegante para o mesmo problema [Raynal, 1986]. O algoritmo de Peterson pode ser resumido no cdigo a seguir:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

int turn = 0 ; // indica de quem a vez int wants[2] = {0, 0} ; // indica se a tarefa i quer acessar a seo crtica void enter (int task) // task pode valer 0 ou 1 { int other = 1 - task ; // indica a outra tarefa wants[task] = 1 ; // task quer acessar a seo crtica turn = task ; while ((turn == task) && wants[other]) ; // espera ocupada } void leave (int task) { wants[task] = 0 ; }

// task libera a seo crtica

c prof. Carlos Maziero

Instrues Test-and-Set 10

Os algoritmos de Dekker e de Peterson foram desenvolvidos para garantir a excluso mtua entre duas tarefas, garantindo tambm o critrio de espera limitada2 . Diversas generalizaes para n tarefas podem ser encontradas na literatura [Raynal, 1986], sendo a mais conhecida delas o algoritmo da padaria, proposto por Leslie Lamport [Lamport, 1974].

5.4

Instrues Test-and-Set

O uso de uma varivel busy para controlar a entrada em uma seo crtica uma idia interessante, que infelizmente no funciona porque o teste da varivel busy e seu ajuste so feitos em momentos distintos do cdigo, permitindo condies de corrida. Para resolver esse problema, projetistas de hardware criaram instrues em cdigo de mquina que permitem testar e atribuir um valor a uma varivel de forma atmica ou indivisvel, sem possibilidade de troca de contexto entre essas duas operaes. A execuo atmica das operaes de teste e atribuio (Test & Set instructions) impede a ocorrncia de condies de disputa. Uma implementao bsica dessa idia est na instruo de mquina Test-and-Set Lock (TSL), cujo comportamento descrito pelo seguinte pseudo-cdigo (que executado atomicamente pelo processador):

TSL(x) :

old x x1 return(old)

A implementao das primitivas enter e leave usando a instruo TSL assume a seguinte forma:
1 2 3 4 5 6 7 8 9 10 11

int lock = 0 ;

// varivel de trava

void enter (int *lock) // passa o endereo da trava { while ( TSL (*lock) ) ; // espera ocupada } void leave (int *lock) { (*lock) = 0 ; }

// libera a seo crtica

Outros processadores oferecem uma instruo que efetua a troca atmica de contedo (swapping) entre dois registradores, ou entre um registrador e uma posio de memria. No caso da famlia de processadores Intel i386 (incluindo o Pentium e seus sucessores), a instruo de troca se chama XCHG (do ingls exchange) e sua funcionalidade pode ser resumida assim:
Este algoritmo pode falhar quando usado em algumas mquinas multi-ncleo ou multi-processadas, pois algumas arquiteturas permitem acesso fora de ordem memria, ou seja, permitem que operaes de leitura na memria se antecipem a operaes de escrita executadas posteriormente, para obter mais desempenho. Este o caso dos processadores Pentium e AMD.
2

c prof. Carlos Maziero

Problemas 11

XCHG op1 , op2 : op1

op2

A implementao das primitivas enter e leave usando a instruo XCHG um pouco mais complexa:
1 2 3 4 5 6 7 8 9 10 11 12 13

int lock ; enter (int *lock) { int key = 1 ; while (key) XCHG (lock, &key) ; } leave (int *lock) { (*lock) = 0 ; }

// varivel de trava

// varivel auxiliar (local) // espera ocupada // alterna valores de lock e key

// libera a seo crtica

Os mecanismos de excluso mtua usando instrues atmicas no estilo TSL so amplamente usados no interior do sistema operacional, para controlar o acesso a sees crticas internas do ncleo, como descritores de tarefas, buers de arquivos ou de conexes de rede, etc. Nesse contexto, eles so muitas vezes denominados spinlocks. Todavia, mecanismos de espera ocupada so inadequados para a construo de aplicaes de usurio, como ser visto a seguir.

5.5

Problemas

Apesar das solues para o problema de acesso seo crtica usando espera ocupada garantirem a excluso mtua, elas sofrem de alguns problemas que impedem seu uso em larga escala nas aplicaes de usurio: Inecincia : as tarefas que aguardam o acesso a uma seo crtica cam testando continuamente uma condio, consumindo tempo de processador sem necessidade. O procedimento adequado seria suspender essas tarefas at que a seo crtica solicitada seja liberada. Injustia : no h garantia de ordem no acesso seo crtica; dependendo da durao de quantum e da poltica de escalonamento, uma tarefa pode entrar e sair da seo crtica vrias vezes, antes que outras tarefas consigam acess-la. Por estas razes, as solues com espera ocupada so pouco usadas na construo de aplicaes. Seu maior uso se encontra na programao de estruturas de controle de concorrncia dentro do ncleo do sistema operacional (onde se chamam spinlocks), e na construo de sistemas de computao dedicados, como controladores embarcados mais simples.

c prof. Carlos Maziero

Semforos 12

Semforos

Em 1965, o matemtico holands E. Dijkstra props um mecanismo de coordenao eciente e exvel para o controle da excluso mtua entre n tarefas: o semforo [Raynal, 1986]. Apesar de antigo, o semforo continua sendo o mecanismo de sincronizao mais utilizado na construo de aplicaes concorrentes, sendo usado de forma explcita ou implcita (na construo de mecanismos de coordenao mais abstratos, como os monitores). Um semforo pode ser visto como uma varivel s, que representa uma seo crtica e cujo contedo no diretamente acessvel ao programador. Internamente, cada semforo contm um contador inteiro s.counter e uma la de tarefas s.queue, inicialmente vazia. Sobre essa varivel podem ser aplicadas duas operaes atmicas, descritas a seguir: Down(s) : usado para solicitar acesso seo crtica associada a s. Caso a seo esteja livre, a operao retorna imediatamente e a tarefa pode continuar sua execuo; caso contrrio, a tarefa solicitante suspensa e adicionada la do semforo; o contador associado ao semforo decrementado3 . Dijkstra denominou essa operao P(s) (do holands probeer, que signica tentar). Down(s): // a executar de forma atmica s.counter s.counter 1 if s.counter < 0 then pe a tarefa corrente no nal de s.queue suspende a tarefa corrente end if Up(s) : invocado para liberar a seo crtica associada a s; o contador associado ao semforo incrementado. Caso a la do semforo no esteja vazia, a primeira tarefa da la acordada, sai da la do semforo e volta la de tarefas prontas para retomar sua execuo. Essa operao foi inicialmente denominada V(s) (do holands verhoog, que signica incrementar). Deve-se observar que esta chamada no bloqueante: a tarefa no precisa ser suspensa ao execut-la. Up(s): // a executar de forma atmica s.counter s.counter + 1 if s.counter 0 then retira a primeira tarefa t de s.queue devolve t la de tarefas prontas (ou seja, acorda t) end if As operaes de acesso aos semforos so geralmente implementadas pelo ncleo do sistema operacional, na forma de chamadas de sistema. importante observar que a execuo das operaes Down(s) e Up(s) deve ser atmica, ou seja, no devem ocorrer acessos concorrentes s variveis internas do semforo, para evitar condies
Alguns sistemas implementam tambm a chamada TryDown(s), cuja semntica no-bloqueante: caso o semforo solicitado esteja ocupado, a chamada retorna imediatamente, com um cdigo de erro.
3

c prof. Carlos Maziero

Semforos 13

de disputa sobre as mesmas. Para garantir a atomicidade dessas operaes em um sistema monoprocessador, seria suciente inibir as interrupes durante a execuo das mesmas; no caso de sistemas com mais de um ncleo, torna-se necessrio usar outros mecanismos de controle de concorrncia, como operaes TSL, para proteger a integridade interna do semforo. Nestes casos, a espera ocupada no constitui um problema, pois a execuo dessas operaes muito rpida. Usando semforos, o cdigo de depsito em conta bancria apresentado na Seo 2 poderia ser reescrito da seguinte forma:
1 2 3 4 5 6 7 8 9 10 11 12 13

typedef struct conta_t { int saldo ; sem_t s = 1; ... } conta_t ;

// saldo atual da conta // semforo associado conta, valor inicial 1 // outras informaes da conta

void depositar (conta_t * conta, int valor) { down (conta->s) ; // solicita acesso conta conta->saldo += valor ; // seo crtica up (conta->s) ; // libera o acesso conta }

A suspenso das tarefas que aguardam o acesso seo crtica elimina a espera ocupada, o que torna esse mecanismo mais eciente no uso do processador que os anteriores. A la de tarefas associada ao semforo contm todas as tarefas que foram suspensas ao solicitar acesso seo crtica usando a chamada Down(s). Como a la obedece uma poltica FIFO, garante-se a tambm a justia no acesso seo crtica, pois todos os processos que aguardam no semforo sero atendidos em sequncia4 . Por sua vez, o valor inteiro associado ao semforo funciona como um contador de recursos: caso seja positivo, indica quantas instncias daquele recurso esto disponveis. Caso seja negativo, indica quantas tarefas esto aguardando para usar aquele recurso. Seu valor inicial permite expressar diferentes situaes de sincronizao, como ser visto na Seo 9. A listagem a seguir apresenta um exemplo hipottico de uso de um semforo para controlar o acesso a um estacionamento. O valor inicial do semforo vagas representa o nmero de vagas inicialmente livres no estacionamento (500). Quando um carro deseja entrar no estacionamento ele solicita uma vaga; enquanto o semforo for positivo no havero bloqueios, pois h vagas livres. Caso no existam mais vagas livres, a chamada carro_entra() car bloqueada at que alguma vaga seja liberada, o que ocorre quando outro carro acionar a chamada carro_sai(). Esta soluo simples pode ser aplicada a um estacionamento com vrias entradas e vrias sadas simultneas.
Algumas implementaes de semforos acordam uma tarefa aleatria da la, no necessariamente a primeira tarefa. Essas implementaes so chamadas de semforos fracos, por no garantirem a justia no acesso seo crtica nem a ausncia de inanio (starvation) de tarefas.
4

c prof. Carlos Maziero

Semforos 14

1 2 3 4 5 6 7 8 9 10 11 12 13

sem_t vagas = 500 ; void carro_entra () { down (vagas) ; ... } void carro_sai () { up (vagas) ; ... }

// solicita uma vaga de estacionamento // demais aes especficas da aplicao

// libera uma vaga de estacionamento // demais aes especficas da aplicao

A API POSIX dene vrias chamadas para a criao e manipulao de semforos. As chamadas mais frequentemente utilizadas esto indicadas a seguir:
1 2 3 4 5 6 7 8 9 10 11 12 13

#include <semaphore.h> // inicializa um semforo apontado por "sem", com valor inicial "value" int sem_init(sem_t *sem, int pshared, unsigned int value); // Operao Up(s) int sem_post(sem_t *sem); // Operao Down(s) int sem_wait(sem_t *sem); // Operao TryDown(s), retorna erro se o semforo estiver ocupado int sem_trywait(sem_t *sem);

Os semforos nos quais o contador inteiro pode assumir qualquer valor so denominados semforos genricos e constituem um mecanismo de coordenao muito poderoso. No entanto, Muitos ambientes de programao, bibliotecas de threads e at mesmo ncleos de sistema provem uma verso simplicada de semforos, na qual o contador s assume dois valores possveis: livre (1) ou ocupado (0). Esses semforos simplicados so chamados de mutexes (uma abreviao de mutual exclusion) ou semforos binrios. Por exemplo, algumas das funes denidas pelo padro POSIX [Gallmeister, 1994, Barney, 2005] para criar e usar mutexes so:

c prof. Carlos Maziero

Variveis de condio 15

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

#include <pthread.h> // inicializa uma varivel do tipo mutex, usando um struct de atributos int pthread_mutex_init (pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); // destri uma varivel do tipo mutex int pthread_mutex_destroy (pthread_mutex_t *mutex); // solicita acesso seo crtica protegida pelo mutex; // se a seo estiver ocupada, bloqueia a tarefa int pthread_mutex_lock (pthread_mutex_t *mutex); // solicita acesso seo crtica protegida pelo mutex; // se a seo estiver ocupada, retorna com status de erro int pthread_mutex_trylock (pthread_mutex_t *mutex); // libera o acesso seo crtica protegida pelo mutex int pthread_mutex_unlock (pthread_mutex_t *mutex);

Variveis de condio

Alm dos semforos, outro mecanismo de sincronizao de uso frequente so as variveis de condio, ou variveis condicionais. Uma varivel de condio no representa um valor, mas uma condio, que pode ser aguardada por uma tarefa. Quando uma tarefa aguarda uma condio, ela colocada para dormir at que a condio seja verdadeira. Assim, a tarefa no precisa testar continuamente aquela condio, evitando uma espera ocupada. Uma tarefa aguardando uma condio representada pela varivel de condio c pode car suspensa atravs do operador wait(c), para ser noticada mais tarde, quando a condio se tornar verdadeira. Essa noticao ocorre quando outra tarefa chamar o operador notify(c) (tambm chamado signal(c)). Por denio, uma varivel de condio c est sempre associada a um semforo binrio c.mutex e a uma la c.queue. O mutex garante a excluso mtua sobre a condio representada pela varivel de condio, enquanto a la serve para armazenar em ordem as tarefas que aguardam aquela condio. Uma implementao hipottica5 para as operaes wait, notify e broadcast (que notica todas as tarefas na espera da condio) para uma tarefa t, seria a seguinte:
Assim como os operadores sobre semforos, os operadores sobre variveis de condio tambm devem ser implementados de forma atmica.
5

c prof. Carlos Maziero

Variveis de condio 16

1 2 3 4 5 6 7 8 9 10 11

wait (c): c.queue t unlock (c.mutex) suspend (t) lock (c.mutex)

// // // //

coloca a tarefa t no fim de c.queue libera o mutex pe a tarefa atual para dormir quando acordar, obtm o mutex imediatamente

notify (c): awake (first (c.queue)) // acorda a primeira tarefa da fila c.queue broadcast (c): awake (c.queue)

// acorda todas as tarefas da fila c.queue

No exemplo a seguir, a tarefa A espera por uma condio que ser sinalizada pela tarefa B. A condio de espera pode ser qualquer: um novo dado em um buer de entrada, a concluso de um procedimento externo, a liberao de espao em disco, etc.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

Task A () { ... lock (c.mutex) while (not condition) wait (c) ; ... unlock (c.mutex) ... } Task B () { ... lock (c.mutex) condition = true notify (c) unlock (c.mutex) ... }

importante observar que na denio original de variveis de condio (conhecida como semntica de Hoare), a operao notify(c) fazia com que a tarefa noticadora perdesse imediatamente o semforo e o controle do processador, que eram devolvidos primeira tarefa da la de c. Como essa semntica complexa de implementar e interfere diretamente no escalonador de processos, as implementaes modernas de variveis de condio normalmente adotam a semntica Mesa [Lampson and Redell, 1980], proposta na linguagem de mesmo nome. Nessa semntica, a operao notify(c) apenas acorda as tarefas que esperam pela condio, sem suspender a execuo da tarefa corrente. Cabe ao programador garantir que a tarefa corrente vai liberar o mutex e no vai alterar o estado associado varivel de condio. As variveis de condio esto presentes no padro POSIX, atravs de operadores como pthread_cond_wait, pthread_cond_signal e pthread_cond_broadcast. O padro POSIX adota a semntica Mesa.

c prof. Carlos Maziero

Monitores 17

Monitores

Ao usar semforos, um programador dene explicitamente os pontos de sincronizao necessrios em seu programa. Essa abordagem ecaz para programas pequenos e problemas de sincronizao simples, mas se torna invivel e suscetvel a erros em sistemas mais complexos. Por exemplo, se o programador esquecer de liberar um semforo previamente alocado, o programa pode entrar em um impasse (vide Seo 10). Por outro lado, se ele esquecer de requisitar um semforo, a excluso mtua sobre um recurso pode ser violada. Em 1972, os cientistas Per Brinch Hansen e Charles Hoare deniram o conceito de monitor [Lampson and Redell, 1980]. Um monitor uma estrutura de sincronizao que requisita e libera a seo crtica associada a um recurso de forma transparente, sem que o programador tenha de se preocupar com isso. Um monitor consiste de: um recurso compartilhado, visto como um conjunto de variveis internas ao monitor. um conjunto de procedimentos que permitem o acesso a essas variveis; um mutex ou semforo para controle de excluso mtua; cada procedimento de acesso ao recurso deve obter o semforo antes de iniciar e liberar o semforo ao concluir; um invariante sobre o estado interno do recurso. O pseudo-cdigo a seguir dene um monitor para operaes sobre uma conta bancria (observe sua semelhana com a denio de uma classe em programao orientada a objetos):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

monitor conta { float saldo = 0.0 ; void depositar (float valor) { if (valor >= 0) conta->saldo += valor ; else error ("erro: valor negativo\n") ; } void retirar (float saldo) { if (valor >= 0) conta->saldo -= valor ; else error ("erro: valor negativo\n") ; } }

A denio formal de monitor prev e existncia de um invariante, ou seja, uma condio sobre as variveis internas do monitor que deve ser sempre verdadeira. No

c prof. Carlos Maziero

Problemas clssicos de coordenao 18

caso da conta bancria, esse invariante poderia ser o seguinte: O saldo atual deve ser a soma de todos os depsitos efetuados e todas as retiradas efetuadas (com sinal negativo). Entretanto, a maioria das implementaes de monitor no suporta a denio de invariantes, com exceo da linguagem Eiel. De certa forma, um monitor pode ser visto como um objeto que encapsula o recurso compartilhado, com procedimentos (mtodos) para acess-lo. No entanto, a execuo dos procedimentos feita com excluso mtua entre eles. As operaes de obteno e liberao do semforo so inseridas automaticamente pelo compilador do programa em todos os pontos de entrada e sada do monitor (no incio e nal de cada procedimento), liberando o programador dessa tarefa e assim evitando erros. Monitores esto presentes em vrias linguagens, como Ada, C#, Eiel, Java e Modula-3. O cdigo a seguir mostra um exemplo simplicado de uso de monitor em Java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

class Conta { private float saldo = 0; public synchronized void depositar (float valor) { if (valor >= 0) saldo += valor ; else System.err.println("valor negativo"); } public synchronized void retirar (float valor) { if (valor >= 0) saldo -= valor ; else System.err.println("valor negativo"); } }

Em Java, a clusula synchronized faz com que um semforo seja associado aos mtodos indicados, para cada objeto (ou para cada classe, se forem mtodos de classe). No exemplo anterior, apenas um depsito ou retirada de cada vez poder ser feito sobre cada objeto da classe Conta. Variveis de condio podem ser usadas no interior de monitores (na verdade, os dois conceitos nasceram juntos). Todavia, devido s restries da semntica Mesa, um procedimento que executa a operao notify em uma varivel de condio deve concluir e sair imediatamente do monitor, para garantir que o invariante associado ao estado interno do monitor seja respeitado [Birrell, 2004].

Problemas clssicos de coordenao

Algumas situaes de coordenao entre atividades ocorrem com muita frequncia na programao de sistemas complexos. Os problemas clssicos de coordenao retratam muitas dessas situaes e permitem compreender como podem ser implementadas

c prof. Carlos Maziero

O problema dos produtores/consumidores 19

suas solues. Nesta seo sero estudados trs problemas clssicos: o problema dos produtores/consumidores, o problema dos leitores/escritores e o jantar dos lsofos. Diversos outros problemas clssicos so frequentemente descritos na literatura, como o problema dos fumantes e o do barbeiro dorminhoco, entre outros [Raynal, 1986, Ben-Ari, 1990]. Uma extensa coletnea de problemas de coordenao (e suas solues) apresentada em [Downey, 2008] (disponvel online).

9.1

O problema dos produtores/consumidores

Este problema tambm conhecido como o problema do buer limitado, e consiste em coordenar o acesso de tarefas (processos ou threads) a um buer compartilhado com capacidade de armazenamento limitada a N itens (que podem ser inteiros, registros, mensagens, etc). So considerados dois tipos de processos com comportamentos simtricos: Produtor : periodicamente produz e deposita um item no buer, caso o mesmo tenha uma vaga livre. Caso contrrio, deve esperar at que surja uma vaga no buer. Ao depositar um item, o produtor consome uma vaga livre. Consumidor : continuamente retira um item do buer e o consome; caso o buer esteja vazio, aguarda que novos itens sejam depositados pelos produtores. Ao consumir um item, o consumidor produz uma vaga livre. Deve-se observar que o acesso ao buer bloqueante, ou seja, cada processo ca bloqueado at conseguir fazer seu acesso, seja para produzir ou para consumir um item. A gura 5 ilustra esse problema, envolvendo vrios produtores e consumidores acessando um buer com capacidade para 12 entradas. interessante observar a forte similaridade dessa gura com a gura ??; na prtica, a implementao de mailboxes e de pipes geralmente feita usando um esquema de sincronizao produtor/consumidor.
c1
C

p1

buer
D L H E I F J G B

c2
A

p2

c3

Figura 5: O problema dos produtores/consumidores. A soluo do problema dos produtores/consumidores envolve trs aspectos de coordenao distintos: A excluso mtua no acesso ao buer, para evitar condies de disputa entre produtores e/ou consumidores que poderiam corromper seu contedo.

c prof. Carlos Maziero

O problema dos leitores/escritores 20

O bloqueio dos produtores no caso do buer estar cheio: os produtores devem aguardar at surjam vagas livres no buer. O bloqueio dos consumidores no caso do buer estar vazio: os consumidores devem aguardar at surjam novos itens a consumir no buer. A soluo para esse problema exige trs semforos, um para atender cada aspecto de coordenao acima descrito. O cdigo a seguir ilustra de forma simplicada uma soluo para esse problema, considerando um buer com capacidade para N itens, inicialmente vazio:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

sem_t mutex ; // controla o acesso ao buffer (inicia em 1) sem_t item ; // nmero de itens no buffer (inicia em 0) sem_t vaga ; // nmero de vagas livres no buffer (inicia em N) produtor () { while (1) { ... sem_down(&vaga) ; sem_down(&mutex) ; ... sem_up(&mutex) ; sem_up(&item) ; } } consumidor () { while (1) { sem_down(&item) ; sem_down(&mutex) ; ... sem_up(&mutex) ; sem_up(&vaga) ; ... } }

// // // // // //

produz um item aguarda uma vaga no buffer aguarda acesso exclusivo ao buffer deposita o item no buffer libera o acesso ao buffer indica a presena de um novo item no buffer

// // // // // //

aguarda um novo item no buffer aguarda acesso exclusivo ao buffer retira o item do buffer libera o acesso ao buffer indica a liberao de uma vaga no buffer consome o item retirado do buffer

importante observar que essa soluo genrica, pois no depende do tamanho do buer, do nmero de produtores ou do nmero de consumidores.

9.2

O problema dos leitores/escritores

Outra situao que ocorre com frequncia em sistemas concorrentes o problema dos leitores/escritores. Neste caso, um conjunto de processos ou threads acessam de forma concorrente uma rea de memria comum (compartilhada), na qual podem fazer leituras ou escritas de valores. As leituras podem ser feitas simultaneamente, pois no interferem umas com as outras, mas as escritas tm de ser feitas com acesso exclusivo rea compartilhada, para evitar condies de disputa. No exemplo da gura 6, os leitores e escritores acessam de forma concorrente uma matriz de inteiros M. Uma soluo trivial para esse problema consistiria em proteger o acesso rea compartilhada com um semforo inicializado em 1; assim, somente um processo por

c prof. Carlos Maziero

O problema dos leitores/escritores 21

l1
M[3]?

e1

M[3]=2

M
3 7 1 9

M?

l2

e2

M=[2,1,0,6]

M[1]?

l3
escritores leitores

Figura 6: O problema dos leitores/escritores. vez poderia acessar a rea, garantindo a integridade de todas as operaes. O cdigo a seguir ilustra essa abordagem simplista:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

sem_t mutex_area ; leitor () { while (1) { sem_down (&mutex_area) ; ... sem_up (&mutex_area) ; ... } } escritor () { while (1) { sem_down (&mutex_area) ; ... sem_up (&mutex_area) ; ... } }

// controla o acesso rea (inicia em 1)

// requer acesso exclusivo rea // l dados da rea compartilhada // libera o acesso rea

// requer acesso exclusivo rea // escreve dados na rea compartilhada // libera o acesso rea

Todavia, essa soluo deixa a desejar em termos de desempenho, porque restringe desnecessariamente o acesso dos leitores rea compartilhada: como a operao de leitura no altera os valores armazenados, no haveria problema em permitir o acesso simultneo de vrios leitores rea compartilhada, desde que as escritas continuem sendo feitas de forma exclusiva. Uma nova soluo para o problema, considerando a possibilidade de acesso simultneo pelos leitores, seria a seguinte:

c prof. Carlos Maziero

O jantar dos lsofos 22

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

sem_t mutex_area ; int conta_leitores = 0 ; sem_t mutex_conta ;

// controla o acesso rea (inicia em 1) // nmero de leitores acessando a rea // controla o acesso ao contador (inicia em 1)

leitor () { while (1) { sem_down (&mutex_conta) ; conta_leitores++ ; if (conta_leitores == 1) sem_down (&mutex_area) ; sem_up (&mutex_conta) ; ... sem_down (&mutex_conta) ; conta_leitores-- ; if (conta_leitores == 0) sem_up (&mutex_area) ; sem_up (&mutex_conta) ; ... } } escritor () { while (1) { sem_down(&mutex_area) ; ... sem_up(&mutex_area) ; ... } }

// // // // //

requer acesso exclusivo ao contador incrementa contador de leitores sou o primeiro leitor a entrar? requer acesso rea libera o contador

// l dados da rea compartilhada // // // // // requer acesso exclusivo ao contador decrementa contador de leitores sou o ltimo leitor a sair? libera o acesso rea libera o contador

// requer acesso exclusivo rea // escreve dados na rea compartilhada // libera o acesso rea

Essa soluo melhora o desempenho das operaes de leitura, mas introduz um novo problema: a priorizao dos leitores. De fato, sempre que algum leitor estiver acessando a rea compartilhada, outros leitores tambm podem acess-la, enquanto eventuais escritores tm de esperar at a rea car livre (sem leitores). Caso existam muito leitores em atividade, os escritores podem car impedidos de acessar a rea, pois ela nunca car vazia. Solues com priorizao para os escritores e solues equitativas entre ambos podem ser facilmente encontradas na literatura [Raynal, 1986, Ben-Ari, 1990]. O relacionamento de sincronizao leitor/escritor encontrado com muita frequncia em aplicaes com mltiplas threads. O padro POSIX dene mecanismos para a criao e uso de travas com essa funcionalidade (com priorizao de escritores), acessveis atravs de chamadas como pthread_rwlock_init, entre outras.

9.3

O jantar dos lsofos

Um dos problemas clssicos de coordenao mais conhecidos o jantar dos lsofos, que foi inicialmente proposto por Dijkstra [Raynal, 1986, Ben-Ari, 1990]. Neste problema, um grupo de cinco lsofos chineses alterna suas vidas entre meditar e comer. Na mesa h um lugar xo para cada lsofo, com um prato, cinco palitos (hashis ou chopsticks) compartilhados e um grande prato de comida ao meio (na verso inicial de Dijkstra, os lsofos compartilhavam garfos e comiam spaguetti). A gura 7 ilustra essa situao.

c prof. Carlos Maziero

O jantar dos lsofos 23

f4 p4 p0

f3

f0

p1 p3 f2 p2 f1

Figura 7: O jantar dos lsofos chineses. Para comer, um lsofo fi precisa pegar os palitos sua direita (pi ) e sua esquerda (pi+1 ), um de cada vez. Como os palitos so compartilhados, dois lsofos vizinhos nunca podem comer ao mesmo tempo. Os lsofos no conversam entre si nem podem observar os estados uns dos outros. O problema do jantar dos lsofos representativo de uma grande classe de problemas de sincronizao entre vrios processos e vrios recursos sem usar um coordenador central. A listagem a seguir representa uma implementao do comportamento bsico dos lsofos, na qual cada palito representado por um semforo:
1 2 3 4 5 6 7 8 9 10 11 12 13

#define NUMFILO 5 sem_t hashi [NUMFILO] ; // um semforo para cada palito (iniciam em 1) filosofo (int i) { while (1) { medita () ; sem_down (&hashi [i]) ; sem_down (&hashi [(i+1) % NUMFILO]) ; come () ; sem_up (&hashi [i]) ; sem_up (&hashi [(i+1) % NUMFILO]) ; } }

// obtem palito i // obtem palito i+1 // devolve palito i // devolve palito i+1

Resolver o problema do jantar dos lsofos consiste em encontrar uma forma de coordenar suas atividades de maneira que todos os lsofos consigam meditar e comer. As solues mais simples para esse problema podem provocar impasses, nos quais todos os lsofos cam bloqueados (impasses sero estudados na Seo 10). Outras solues

c prof. Carlos Maziero

Impasses 24

podem provocar inanio (starvation), ou seja, alguns dos lsofos nunca conseguem comer. A gura 8 apresenta os lsofos em uma situao de impasse: cada lsofo obteve o palito sua direita e est aguardando o palito sua esquerda (indicado pelas setas tracejadas). Como todos os lsofos esto aguardando, ningum mais consegue executar.

p4

f4

p0 f3 p3 f0

f2 p2 f1

p1

Figura 8: Um impasse no jantar dos lsofos chineses. Uma soluo trivial para o problema do jantar dos lsofos consiste em colocar um saleiro hipottico sobre a mesa: quando um lsofo deseja comer, ele deve pegar o saleiro antes de obter os palitos; assim que tiver ambos os palitos, ele devolve o saleiro mesa e pode comer. Obviamente, essa soluo serializa o acesso aos palitos e por isso tem baixo desempenho. Imagine como essa soluo funcionaria em uma situao com 1000 lsofos e 1000 palitos? Diversas outras solues podem ser encontradas na literatura [Tanenbaum, 2003, Silberschatz et al., 2001].

10

Impasses

O controle de concorrncia entre tarefas acessando recursos compartilhados implica em suspender algumas tarefas enquanto outras acessam os recursos, de forma a garantir a consistncia dos mesmos. Para isso, a cada recurso associado um semforo ou outro mecanismo equivalente. Assim, as tarefas solicitam e aguardam a liberao de cada semforo para poder acessar o recurso correspondente. Em alguns casos, o uso de semforos pode levar a situaes de impasse (ou deadlock), nas quais todas as tarefas envolvidas cam bloqueadas aguardando a liberao de

c prof. Carlos Maziero

Impasses 25

semforos, e nada mais acontece. Para ilustrar uma situao de impasse, ser utilizado o exemplo de acesso a uma conta bancria apresentado na Seo 2. O cdigo a seguir implementa uma operao de transferncia de fundos entre duas contas bancrias. A cada conta est associado um semforo, usado para prover acesso exclusivo aos dados da conta e assim evitar condies de disputa:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

typedef struct conta_t { int saldo ; sem_t lock ; ... } conta_t ;

// saldo atual da conta // semforo associado conta // outras informaes da conta

void transferir (conta_t* contaDeb, conta_t* contaCred, int valor) { sem_down (contaDeb->lock) ; // obtm acesso a contaDeb sem_down (contaCred->lock) ; // obtm acesso a contCred if (contaDeb->saldo >= valor) { contaDeb->saldo -= valor ; contaCred->saldo += valor ; } sem_up (contaDeb->lock) ; sem_up (contaCred->lock) ; }

// debita valor de contaDeb // credita valor em contaCred // libera acesso a contaDeb // libera acesso a contaCred

Caso dois clientes do banco (representados por duas tarefas t1 e t2 ) resolvam fazer simultaneamente operaes de transferncia entre suas contas (t1 transfere um valor v1 de c1 para c2 e t2 transfere um valor v2 de c2 para c1 ), poder ocorrer uma situao de impasse, como mostra o diagrama de tempo da gura 9.
t1
sem_down (c1->lock) (semforo obtido) sem_down (c2->lock) (semforo obtido) sem_down (c2->lock)

t2

sem_down (c1->lock)

impasse
t t

Figura 9: Impasse entre duas transferncias.

c prof. Carlos Maziero

Caracterizao de impasses 26

Nessa situao, a tarefa t1 detm o semforo de c1 e solicita o semforo de c2 , enquanto t2 detm o semforo de c2 e solicita o semforo de c1 . Como nenhuma das duas tarefas poder prosseguir sem obter o semforo desejado, nem poder liberar o semforo de sua conta antes de obter o outro semforo e realizar a transferncia, se estabelece um impasse (ou deadlock). Impasses so situaes muito frequentes em programas concorrentes, mas tambm podem ocorrer em sistemas distribudos. Antes de conhecer as tcnicas de tratamento de impasses, importante compreender suas principais causas e saber caracteriz-los adequadamente, o que ser estudado nas prximas sees.

10.1

Caracterizao de impasses

Em um impasse, duas ou mais tarefas se encontram bloqueadas, aguardando eventos que dependem somente delas, como a liberao de semforos. Em outras palavras, no existe inuncia de entidades externas em uma situao de impasse. Alm disso, como as tarefas envolvidas detm alguns recursos compartilhados (representados por semforos), outras tarefas que vierem a requisit-los tambm caro bloqueadas, aumentando gradativamente o impasse, o que pode levar o sistema inteiro a parar de funcionar. Formalmente, um conjunto de N tarefas se encontra em um impasse se cada uma das tarefas aguarda um evento que somente outra tarefa do conjunto poder produzir. Quatro condies fundamentais so necessrias para que os impasses possam ocorrer [Coman et al., 1971, Ben-Ari, 1990]: C1 Excluso mtua : o acesso aos recursos deve ser feito de forma mutuamente exclusiva, controlada por semforos ou mecanismos equivalentes. No exemplo da conta corrente, apenas uma tarefa por vez pode acessar cada conta. C2 Posse e espera : uma tarefa pode solicitar o acesso a outros recursos sem ter de liberar os recursos que j detm. No exemplo da conta corrente, cada tarefa detm o semforo de uma conta e solicita o semforo da outra conta para poder prosseguir. C3 No-preempo : uma tarefa somente libera os recursos que detm quando assim o decidir, e no pode perd-los contra a sua vontade (ou seja, o sistema operacional no retira os recursos j alocados s tarefas). No exemplo da conta corrente, cada tarefa detm indenidamente os semforos que j obteve. C4 Espera circular : existe um ciclo de esperas pela liberao de recursos entre as tarefas envolvidas: a tarefa t1 aguarda um recurso retido pela tarefa t2 (formalmente, t1 t2 ), que aguarda um recurso retido pela tarefa t3 , e assim por diante, sendo que a tarefa tn aguarda um recurso retido por t1 . Essa dependncia circular pode ser expressa formalmente da seguinte forma: t1 t2 t3 tn t1 . No exemplo da conta corrente, pode-se observar claramente que t1 t2 t1 . Deve-se observar que essas quatro condies so necessrias para a formao de impasses; se uma delas no for vericada, no existiro impasses no sistema. Por outro lado, no so condies sucientes para a existncia de impasses, ou seja, a

c prof. Carlos Maziero

Grafos de alocao de recursos 27

vericao dessas quatro condies no garante a presena de um impasse no sistema. Essas condies somente so sucientes se existir apenas uma instncia de cada tipo de recurso, como ser discutido na prxima seo.

10.2

Grafos de alocao de recursos

possvel representar gracamente a alocao de recursos entre as tarefas de um sistema concorrente. A representao grca prov uma viso mais clara da distribuio dos recursos e permite detectar visualmente a presena de esperas circulares que podem caracterizar impasses. Em um grafo de alocao de recursos [Holt, 1972], as tarefas so representadas por crculos ( ) e os recursos por retngulos ( ). A posse de um recurso por uma tarefa representada como , enquanto a requisio de um recurso por uma tarefa indicada por . A gura 10 apresenta o grafo de alocao de recursos da situao de impasse ocorrida na transferncia de valores entre contas bancrias da gura 9. Nessa gura percebe-se claramente a dependncia cclica entre tarefas e recursos t1 c2 t2 c1 c1 , que neste caso evidencia um impasse. Como h um s recurso de cada tipo (apenas uma conta c1 e uma conta c2 ), as quatro condies necessrias se mostram tambm sucientes para caracterizar um impasse.
t1 detm c1 c1 t2 requer c1

t1

t2

t1 requer c2

c2

t2 detm c2

Figura 10: Grafo de alocao de recursos com impasse. Alguns recursos lgicos ou fsicos de um sistema computacional podem ter mltiplas instncias: por exemplo, um sistema pode ter duas impressoras idnticas instaladas, o que constituiria um recurso (impressora) com duas instncias equivalentes, que podem ser alocadas de forma independente. No grafo de alocao de recursos, a existncia de mltiplas instncias de um recurso representada atravs de chas dentro dos retngulos. Por exemplo, as duas instncias de impressora seriam indicadas no grafo como . A gura 11 indica apresenta um grafo de alocao de recursos considerando alguns recursos com mltiplas instncias. importante observar que a ocorrncia de ciclos em um grafo de alocao, envolvendo recursos com mltiplas instncias, pode indicar a presena de um impasse, mas no garante sua existncia. Por exemplo, o ciclo t1 r1 t2 r2 t3 r3 t1 presente no diagrama da gura 11 no representa um impasse, porque a qualquer momento a tarefa t4 pode liberar uma instncia do recurso r2 , solicitado por t2 , desfazendo assim o ciclo. Um algoritmo de deteco de impasses envolvendo recursos com mltiplas instncias apresentado em [Tanenbaum, 2003].

c prof. Carlos Maziero


r1

Tcnicas de tratamento de impasses 28

t1

r3 t2

r4

r2 t3 t4

Figura 11: Grafo de alocao com mltiplas instncias de recursos.

10.3

Tcnicas de tratamento de impasses

Como os impasses paralisam tarefas que detm recursos, sua ocorrncia pode incorrer em consequncias graves, como a paralisao gradativa de todas as tarefas que dependam dos recursos envolvidos, o que pode levar paralisao de todo o sistema. Devido a esse risco, diversas tcnicas de tratamento de impasses foram propostas. Essas tcnicas podem denir regras estruturais que previnam impasses, podem atuar de forma pr-ativa, se antecipando aos impasses e impedindo sua ocorrncia, ou podem agir de forma reativa, detectando os impasses que se formam no sistema e tomando medidas para resolv-los. Embora o risco de impasses seja uma questo importante, os sistemas operacionais de mercado (Windows, Linux, Solaris, etc) adotam a soluo mais simples: ignorar o risco, na maioria das situaes. Devido ao custo computacional necessrio ao tratamento de impasses e sua forte dependncia da lgica das aplicaes envolvidas, os projetistas de sistemas operacionais normalmente preferem deixar a gesto de impasses por conta dos desenvolvedores de aplicaes. As principais tcnicas usadas para tratar impasses em um sistema concorrente so: prevenir impasses atravs, de regras rgidas para a programao dos sistemas, impedir impasses, por meio do acompanhamento contnuo da alocao dos recursos s tarefas, e detectar e resolver impasses. Essas tcnicas sero detalhadas nas prximas sees. 10.3.1 Preveno de impasses

As tcnicas de preveno de impasses buscam garantir que impasses nunca possam ocorrer no sistema. Para alcanar esse objetivo, a estrutura do sistema e a lgica das aplicaes devem ser construdas de forma que as quatro condies fundamentais para a ocorrncia de impasses, apresentadas na Seo 10.1, nunca sejam satisfeitas. Se ao menos uma das quatro condies for quebrada por essas regras estruturais, os impasses no podero ocorrer. A seguir, cada uma das condies necessrias analisada de acordo com essa premissa:

c prof. Carlos Maziero

Tcnicas de tratamento de impasses 29

Excluso mtua : se no houver excluso mtua no acesso a recursos, no podero ocorrer impasses. Mas, como garantir a integridade de recursos compartilhados sem usar mecanismos de excluso mtua? Uma soluo interessante usada na gerncia de impressoras: um processo servidor de impresso (printer spooler) gerencia a impressora e atende as solicitaes dos demais processos. Com isso, os processos que desejam usar a impressora no precisam obter acesso exclusivo a esse recurso. A tcnica de spooling previne impasses envolvendo as impressoras, mas no facilmente aplicvel a outros tipos de recurso, como arquivos em disco e reas de memria compartilhada. Posse e espera : caso as tarefas usem apenas um recurso de cada vez, solicitandoo e liberando-o logo aps o uso, impasses no podero ocorrer. No exemplo da transferncia de fundos da gura 9, seria possvel separar a operao de transferncia em duas operaes isoladas: dbito em c1 e crdito em c2 (ou viceversa), sem a necessidade de acesso exclusivo simultneo s duas contas. Com isso, a condio de posse e espera seria quebrada e o impasse evitado. Outra possibilidade seria somente permitir a execuo de tarefas que detenham todos os recursos necessrios antes de iniciar. Todavia, essa abordagem poderia levar as tarefas a reter os recursos por muito mais tempo que o necessrio para suas operaes, degradando o desempenho do sistema. Uma terceira possibilidade seria associar um prazo (time-out) s solicitaes de recursos: ao solicitar um recurso, a tarefa dene um tempo mximo de espera por ele; caso o prazo expire, a tarefa pode tentar novamente ou desistir, liberando os demais recursos que detm. No-preempo : normalmente uma tarefa obtm e libera os recursos de que necessita, de acordo com sua lgica interna. Se for possvel arrancar um recurso da tarefa, sem que esta o libere explicitamente, e devolv-lo mais tarde, impasses envolvendo aquele recurso no podero ocorrer. Essa tcnica frequentemente usada em recursos cujo estado interno pode ser salvo e restaurado de forma transparente para a tarefa, como pginas de memria e o contexto do processador. No entanto, de difcil aplicao sobre recursos como arquivos ou reas de memria compartilhada, porque a preempo viola a excluso mtua e pode deixar inconsistncias no estado interno do recurso. Espera circular : um impasse uma cadeia de dependncias entre tarefas e recursos que forma um ciclo. Ao prevenir a formao de tais ciclos, impasses no podero ocorrer. A estratgia mais simples para prevenir a formao de ciclos ordenar todos os recursos do sistema de acordo com uma ordem global nica, e forar as tarefas a solicitar os recursos obedecendo a essa ordem. No exemplo da transferncia de fundos da gura 9, o nmero de conta bancria poderia denir uma ordem global. Assim, todas as tarefas deveriam solicitar primeiro o acesso conta mais antiga e depois mais recente (ou vice-versa, mas sempre na mesma ordem para todas as tarefas). Com isso, elimina-se a possibilidade de impasses.

c prof. Carlos Maziero

Tcnicas de tratamento de impasses 30

10.3.2

Impedimento de impasses

Uma outra forma de tratar os impasses preventivamente consiste em acompanhar a alocao dos recursos s tarefas e, de acordo com algum algoritmo, negar acessos de recursos que possam levar a impasses. Uma noo essencial nas tcnicas de impedimento de impasses o conceito de estado seguro. Cada estado do sistema denido pela distribuio dos recursos entre as tarefas. O conjunto de todos os estados possveis do sistema forma um grafo de estados, no qual as arestas indicam as alocaes e liberaes de recursos. Um determinado estado considerado seguro se, a partir dele, possvel concluir as tarefas pendentes. Caso o estado em questo somente leve a impasses, ele considerado inseguro. As tcnicas de impedimento de impasses devem portanto manter o sistema sempre em um estado seguro, evitando entrar em estados inseguros. A gura 12 ilustra o grafo de estados do sistema de transferncia de valores com duas tarefas discutido anteriormente. Nesse grafo, cada estado a combinao dos estados individuais das duas tarefas. Pode-se observar no grafo que o estado E13 corresponde a um impasse, pois a partir dele no h mais nenhuma possibilidade de evoluo do sistema a outros estados. Alm disso, os estados E12 , E14 e E15 so considerados estados inseguros, pois levam invariavelmente na direo do impasse. Os demais estados so considerados seguros, pois a partir de cada um deles possvel continuar a execuo e retornar ao estado inicial E0 . Obviamente, transies que levem de um estado seguro a um inseguro devem ser evitadas, como E9 E14 ou E10 E12 . A tcnica de impedimento de impasses mais conhecida o algoritmo do banqueiro, criado por Dijkstra em 1965 [Tanenbaum, 2003]. Esse algoritmo faz uma analogia entre as tarefas de um sistema e os clientes de um banco, tratando os recursos como crditos emprestados s tarefas para a realizao de suas atividades. O banqueiro decide que solicitaes de emprstimo deve atender para conservar suas nanas em um estado seguro. As tcnicas de impedimento de impasses necessitam de algum conhecimento prvio sobre o comportamento das tarefas para poderem operar. Normalmente necessrio conhecer com antecedncia que recursos sero acessados por cada tarefa, quantas instncias de cada um sero necessrias e qual a ordem de acesso aos recursos. Por essa razo, so pouco utilizadas na prtica. 10.3.3 Deteco e resoluo de impasses

Nesta abordagem, nenhuma medida preventiva adotada para prevenir ou evitar impasses. As tarefas executam normalmente suas atividades, alocando e liberando recursos conforme suas necessidades. Quando ocorrer um impasse, o sistema o detecta, determina quais as tarefas e recursos envolvidos e toma medidas para desfaz-lo. Para aplicar essa tcnica, duas questes importantes devem ser respondidas: como detectar os impasses? E como resolv-los? A deteco de impasses pode ser feita atravs da inspeo do grafo de alocao de recursos (Seo 10.2), que deve ser mantido pelo sistema e atualizado a cada alocao ou liberao de recurso. Um algoritmo de deteco de ciclos no grafo deve ser executado periodicamente, para vericar a presena das dependncias cclicas que podem indicar impasses.

c prof. Carlos Maziero

Tcnicas de tratamento de impasses 31

E0

E1

E2

E3

E4

E5

E6

E7

E20

E8

E21

E16

E9

E22

E17

E14

E10

Estados inseguros
E23 E18 E15 E12 E11

E19

E13

impasse

Figura 12: Grafo de estados do sistema de transferncias com duas tarefas. Alguns problemas decorrentes dessa estratgia so o custo de manuteno contnua do grafo de alocao e, sobretudo, o custo de sua anlise: algoritmos de busca de ciclos em grafos tm custo computacional elevado, portanto sua ativao com muita frequncia poder prejudicar o desempenho do sistema. Por outro lado, se a deteco for ativada apenas esporadicamente, impasses podem demorar muito para ser detectados, o que tambm ruim para o desempenho. Uma vez detectado um impasse e identicadas as tarefas e recursos envolvidos, o sistema deve proceder sua resoluo, que pode ser feita de duas formas: Eliminar tarefas : uma ou mais tarefas envolvidas no impasse so eliminadas, liberando seus recursos para que as demais tarefas possam prosseguir. A escolha das tarefas

c prof. Carlos Maziero

Tcnicas de tratamento de impasses 32

a eliminar deve levar em conta vrios fatores, como o tempo de vida de cada uma, a quantidade de recursos que cada tarefa detm, o prejuzo para os usurios, etc. Retroceder tarefas : uma ou mais tarefas envolvidas no impasse tm sua execuo parcialmente desfeita, de forma a fazer o sistema retornar a um estado seguro anterior ao impasse. Para retroceder a execuo de uma tarefa, necessrio salvar periodicamente seu estado, de forma a poder recuperar um estado anterior quando necessrio6 . Alm disso, operaes envolvendo a rede ou interaes com o usurio podem ser muito difceis ou mesmo impossveis de retroceder: como desfazer o envio de um pacote de rede, ou a reproduo de um arquivo de udio? A deteco e resoluo de impasses uma abordagem interessante, mas relativamente pouco usada fora de situaes muito especcas, porque o custo de deteco pode ser elevado e as alternativas de resoluo sempre implicam perder tarefas ou parte das execues j realizadas.

Questes
1. Explique o que espera ocupada e por que os mecanismos que empregam essa tcnica so considerados inecientes. 2. Em que circunstncias o uso de espera ocupada inevitvel? 3. Explique o que so condies de disputa, mostrando um exemplo real. 4. Mostre como pode ocorrer violao da condio de excluso mtua em um semforo se as operaes down e up no forem implementadas de forma atmica. 5. Em que situaes um semforo deve ser inicializado em 0, 1 ou n > 1? 6. Por que no existem operaes read(s) e write(s) para ler ou ajustar o valor corrente de um semforo? 7. Explique cada uma das quatro condies necessrias para a ocorrncia de impasses. 8. Na preveno de impasses, como pode ser feita a quebra da condio de posse e espera? 9. Na preveno de impasses, como pode ser feita a quebra da condio de excluso mtua? 10. Na preveno de impasses, como pode ser feita a quebra da condio de espera circular? 11. Na preveno de impasses, como pode ser feita a quebra da condio de nopreempo? 12. Uma vez detectado um impasse, quais as abordagens possveis para resolv-lo? Explique-as e comente sua viabilidade.
6

Essa tcnica conhecida como checkpointing e os estados anteriores salvos so denominados checkpoints.

c prof. Carlos Maziero

Tcnicas de tratamento de impasses 33

13. Como pode ser detectada a ocorrncia de impasses, considerando disponvel apenas um recurso de cada tipo?

Exerccios
1. Mostre como pode ocorrer violao da condio de excluso mtua se as operaes down(s) e up(s) no forem implementadas de forma atmica. 2. Escreva, em pseudo-cdigo, as operaes down(s) e up(s) sobre semforos, usando instrues TSL para controlar o acesso estrutura de dados que dene cada semforo, que indicada a seguir. No necessrio detalhar as operaes de ponteiros envolvendo a la queue.
1 2 3 4 5 6

struct semaphore { int lock = false ; int count ; task_t *queue ; }

3. Escreva, em pseudo-cdigo, um sistema produtor/consumidor com dois buers limitados, ou seja, X b1 Y e Y b2 Z, onde X, Y e Z so tipos de processos e b1 e b2 so buers com capacidade n1 e n2 , respectivamente, inicialmente vazios. 4. Nos grafos de alocao de recursos da gura 13, indique o(s) ciclo(s) onde existe um impasse:
r1 r1 r4 t3

t1

r2

t2

t1

r3

t4

r2 r3 (a) t2 (b)

Figura 13: Grafos de alocao de recursos. 5. A gura 14 representa uma situao de impasse em um cruzamento de trnsito urbano. Mostre que as quatro condies necessrias para a ocorrncia de impasses esto presentes, e aponte uma regra simples para evitar esta situao, explicando suas razes.

c prof. Carlos Maziero

Tcnicas de tratamento de impasses 34

Figura 14: Impasse em um cruzamento de trnsito. 6. O trecho de cdigo a seguir apresenta uma soluo para o problema do jantar dos lsofos. Explique o cdigo e indique se ele funciona, justicando sua resposta. Que mudana simples pode ser feita no cdigo para melhor-lo ou corrigi-lo?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

#define N 5 sem_t garfo[5] ; // 5 semforos iniciados em 1 int esq (int k) { return k ; } int dir (int k) { return = (k + 1) % N // (k+1) mdulo N } void filosofo (int i) { while (1) { medita (i); sem_down (garfo [esq(i)]) ; sem_down (garfo [dir(i)]) ; come (i); sem_up (garfo [dir(i)]) ; sem_up (garfo [esq(i)]) ; } }

c prof. Carlos Maziero

REFERNCIAS 35

Projetos
1. Implemente uma soluo em C para o problema do produtor/consumidor, usando threads e semforos no padro POSIX. 2. Implemente uma soluo em C para o problema do produtor/consumidor, usando threads e variveis de condio no padro POSIX. 3. Implemente uma soluo em C para o problema dos leitores/escritores com priorizao para escritores, usando threads e semforos POSIX. 4. Implemente uma soluo em C para o problema dos leitores/escritores com priorizao para escritores, usando threads e rwlocks POSIX.

Referncias
[Barney, 2005] Barney, B. (2005). POSIX http://www.llnl.gov/computing/tutorials/pthreads. threads programming.

[Ben-Ari, 1990] Ben-Ari, M. (1990). Principles of Concurrent and Distributed Programming. Prentice-Hall. [Birrell, 2004] Birrell, A. (2004). Implementing condition variables with semaphores. Computer Systems Theory, Technology, and Applications, pages 2937. [Coman et al., 1971] Coman, E., Elphick, M., and Shoshani, A. (1971). System deadlocks. ACM Computing Surveys, 3(2):6778. [Downey, 2008] Downey, A. (2008). The Little Book of Semaphores. Green Tea Press. [Gallmeister, 1994] Gallmeister, B. (1994). POSIX.4: Programming for the Real World. OReilly Media, Inc. [Holt, 1972] Holt, R. (1972). Some deadlock properties of computer systems. ACM Computing Surveys, 4(3):179196. [Lamport, 1974] Lamport, L. (1974). A new solution of Dijkstras concurrent programming problem. Communications of the ACM, 17(8):453455. [Lampson and Redell, 1980] Lampson, B. and Redell, D. (1980). Experience with processes and monitors in Mesa. Communications of the ACM. [Raynal, 1986] Raynal, M. (1986). Algorithms for Mutual Exclusion. The MIT Press. [Silberschatz et al., 2001] Silberschatz, A., Galvin, P., and Gagne, G. (2001). Sistemas Operacionais Conceitos e Aplicaes. Campus. [Tanenbaum, 2003] Tanenbaum, A. (2003). Sistemas Operacionais Modernos, 2a edio. Pearson Prentice-Hall.

Você também pode gostar