Computação Concorrente (MAB117) Gabarito Segunda Prova Prof. Silvana Rossetto 9 de julho de 2015 1 DCC/IM/UFRJ Questão 1 (2,5 pts) O código Java abaixo implementa uma solução para o problema do barbeiro dorminhoco. O barbeiro só pode atender a um cliente de cada vez. Há 5 cadeiras de espera e os clientes são atendidos por ordem de chegada. Em uma aplicação foram implementadas threads para os clientes e para o barbeiro. A thread dos clientes chama repetidamente o método EsperaBarbeiro() até que ele retorne true. A thread do barbeiro chama os métodos EsperaCliente e TerminaCliente N vezes (N=n o de threads cliente). (a) Se executarmos essa aplicação com 10 threads cliente e uma thread barbeiro ela funcionará corretamente? (b) A chamada notifyall() na linha 14 pode ser substituída por uma chamada notify()? (c) A linha 24 pode ser substituída pela linha if(this.esperando > 0) notifyall(); sem alterar a corretude da solução? Justifique suas respostas. Respostas sem justificativa não serão consideradas. 1: class Cadeira { //...declaração de variaveis 2: Cadeira() { this.esperando = 0; //numero de clientes esperando (no maximo 5) 3: this.ocupado = 0; //estado do barbeiro (0: livre; 1: ocupado) 4: this.proximocliente = 0; //senha do cliente no inicio da fila de espera 5: this.ultimocliente = 0; //proxima senha do cliente que entrar na fila 6: 7: public synchronized boolean EsperaBarbeiro () { 8: if(this.esperando == 5) return false; 9: int minhasenha = this.ultimocliente; 10: this.ultimocliente++; 11: this.esperando++; 12: while(this.proximocliente!= minhasenha) wait(); 13: this.ocupado = 1; 14: notifyall(); 15: return true; 16: 17: public synchronized void EsperaCliente () { 18: while((this.esperando == 0) (this.ocupado == 0)) wait(); 19: this.esperando--; 20: 21: public synchronized void TerminaCliente () { 22: this.ocupado = 0; 23: this.proximocliente++; 24: notifyall(); 25: 26: Resp.: (a) Vamos considerar todas as possíveis combinações de execução das threads. Se o barbeiro começar primeiro, ele chamará o método EsperaCliente(), como o valor do atributo esperando será 0, o barbeiro irá se bloquear. Quando o primeiro cliente chegar, o atributo proximocliente será igual ao atributo minhasenha, então ele alterará o atributo ocupado para 1, desbloqueará o barbeiro e estará pronto para ser atendido. O barbeiro irá verificar que as duas condições do while avaliam falso, decrementará o número de clientes esperando e estará pronto para atender o cliente. Se chegarem em seguida todos os outros clientes, 5 deles ficarão bloqueados porque chamarão a função wait(), uma vez que o atributo proximocliente não será igual
ao atributo minhasenha de cada um deles. Os demais clientes que tentarem entrar, sairão pela linha 8. Quando o barbeiro terminar com o primeiro cliente, ele chamará o método TerminaCliente() onde alterará o atributo ocupado para 0 (sinalizando que está livre) e incrementará o atributo proximocliente, possibilitando que apenas o segundo cliente a entrar na barbearia consiga deixar o while da linha 12. Em seguida ele chama o método notifyall() para desbloquear todos os clientes esperando. Se logo em seguida o barbeiro conseguir executar o método EsperaCliente() (antes de qualquer cliente voltar), o barbeiro irá se bloquear na linha 18 (pois o atributo ocupado é igual a 0). Quando o segundo cliente conseguir voltar a executar ele alterará novamente o atributo ocupado para 1 e através da chamada a notifyall() irá acordar o barbeiro que sairá do while da linha 18. Por outro lado, se antes do barbeiro executar o método EsperaCliente(), o segundo cliente conseguir executar após ser desbloquado, ele marcará ocupado com o valor 1. Então quando o barbeiro executar EsperaCliente(), o while da linha 18 avaliará falso e ele seguirá executando a linha 19. A aplicação então prosseguirá até que todos os clientes sejam atendidos. Se no início algum (ou alguns) clientes chegarem antes do barbeiro, o primeiro cliente alterará o atributo ocupado para 1 e estará pronto para ser atendido. O demais clientes ficarão bloqueados na linha 12. Quando o barbeiro entrar, o while da linha 18 avaliará falso e ele prosseguirá para atender o primeiro cliente. Dessa forma, podemos concluir que a aplicação funcionará corretamente independente do número de threads cliente. (b) Não, a chamada notifyall() na linha 14 não pode ser substituída por uma chamada notify() porque sua finalidade é desbloquear o barbeiro e como é possível ter clientes bloqueados na mesma variável de condição, não garantiríamos que o barbeiro seria desbloqueado sempre. (c) Sim, a linha 24 pode ser substituída pela linha if(this.esperando > 0) notifyall(); sem alterar a corretude da solução pois o barbeiro só precisa desbloquear os clientes se houver clientes esperando. Questão 2 (2,5 pts) Usando semáforos em C, implemente a função void barreira(int numthreads) para oferecer uma solução de sincronização por barreira (ou sincronização coletiva). O argumento numthreads informa o número de threads que deverão chamar a função barreira a cada iteração, ou seja, sempre que uma thread chamar essa função ela deverá ficar bloqueada até que todas as threads (numthreads) chamem a mesma função. sem_t mutex, cond; int bloqueadas=0; //inicializacao na main... sem_init(&mutex,0,1); //semaforo para exclusao mutua sem_init(&cond,0,0); //semaforo para bloqueio incondicional void barreira(int numthreads) { sem_wait(&mutex); //esclusão mútua bloqueadas++; //indica que mais uma thread chegou if (bloqueadas < numthreads) { //verifica se é a última thread sem_post(&mutex); //nao é a ultima thread, libera a exclusao mutua sem_wait(&cond); //se bloqueia (para aguardar todas as threads) bloqueadas--; //depois de ser desbloqueada, decrementa o numero de threads //dessa iteracao if (bloqueadas==0) sem_post(&mutex); //se for a ultima thread a sair, //libera a exclusao mutua
else sem_post(&cond); //se nao for a ultima thread a sair, desbloqueia outra thread else { //se for a ultima thread a chegar na barreira, desbloqueia uma thread //e nao libera a exclusao mutua para garantir que todas as threads sairao //da barreira antes das threads conseguirem iniciar a proxima barreira bloqueadas--; sem_post(&cond); Questão 3 (2,5 pts) O código abaixo implementa uma solução para o problema dos leitores e escritores, com prioridade para escritores (sempre que um escritor está esperando para escrever, novos leitores não podem começar a ler). (a) Esse código pode levar a aplicação a uma situação de deadlock? (b) Esse código garante a prioridade para escritores? Justifique suas respostas. Respostas sem justificativa não serão consideradas. Resp.: (a) Não, esse código não levará a aplicação a uma situação de deadlock. Uma situação de deadlock ocorre quando uma ou mais threads da aplicação ficam bloqueadas e as threads que poderiam desbloqueá-las também estão bloqueadas ou finalizadas. Todos os semáforos são inicializados com um sinal. O semáforo usado para exclusão mútua ( em ) é sempre decrementado e incrementado pelas threads. Na primeira parte do código dos leitores, as threads incrementam o semáforo de exclusão mútua antes de
se bloquarem no semáforo de prioridade ( prior ) nas linhas 5 e 6. Sempre que uma thread leitora é desbloqueada do semáforo de prioridade ela incrementa esse semáforo garantindo assim que todas as threads leitoras que estão aguardando operações de escrita sejam em algum momento desbloqueadas. Apenas a primeira thread leitora (em cada bloco de execução das threads leitoras) decrementa o semáforo de escrita ( escrita ), isso garante que quando o escritor terminar de escrever, o incremento feito por ele no semáforo de escrita será suficiente para liberar todas as threads leitoras em espera. Da mesma forma, quando todas as threads leitoras terminam um bloco de execução, apenas um incremento é feito no semáforo de escrita permitindo que threads escritoras executem ou que um novo bloco de leitores se inicie. Do lado dos escritores, o decremento no semáforo de prioridade nunca causará bloqueio na thread pois apenas a primeira thread escritora em espera fará isso e o semáforo necessariamente terá um sinal. Posteriomente, como o semáforo de prioridade só é incrementado quando a última thread escritora em espera conclui sua execução, ele voltará a ter um sinal. Sendo assim, podemos concluir que não há possibilidade de bloqueios indefinidos das threads dessa aplicação. (b) Sim, esse código garante a prioridade para escritores porque sempre que um escritor quer escrever ele deixa sinalizado na variável num escresp que há um escritor esperando. Se ele for o primeiro escritor na fila, ele também consumirá o sinal do semáforo prior e apenas quando o último escritor da fila conseguir escrever esse semáforo será incrementado. Os leitores, por sua vez, sempre verificam o valor da variável num escresp antes de começarem a ler. Quando há escritores esperando, os leitores necessariamente se bloquearão na linha 6 (código dos leitores) pois o semáforo prior não terá sinais sobrando. Além disso não é possível um leitor chegar até a linha 12 do código dos leitores se já houver escritor esperando ou escrevendo. Questão 4 (2.5 pts) Para dançar a quadrilha de São João é preciso formar um par (um homem e uma mulher). Considere uma aplicação com threads homem e mulher. Escreva uma classe Java (monitor) que ofereça os métodos entramulher() e entrahomem() os quais deverão ser chamados pelas threads mulher e homem, respectivamente, antes de entrarem na quadrilha. O métodos devem garantir que cada thread bloqueie até formar um par e que as threads deixem o monitor sempre em pares. class Pares { private int m, h, formandoparm, formandoparh; Pares() { this.m = 0; //numero de mulheres esperando um par this.h = 0; //numero de homens esperando um par this.formandoparm = 0; //se==1, uma mulher já foi selecionada para o proximo par this.formandoparh = 0; //se==1, um homem já foi selecionado para o proximo par // Barreira para mulheres public synchronized void entramulher (int id) { try { this.m++; while ((this.h == 0) (this.formandoparm == 1)) { wait(); //bloqueia pela condicao logica da aplicacao this.formandoparm = 1; //uma mulher foi selecionada para formar um par
if(this.formandoparh == 0) { notifyall(); //desbloqueia os homens esperando par else { this.m--; this.h--; //um par foi formado this.formandoparm = 0; this.formandoparh = 0; //termina a formacao do par catch (InterruptedException e) { // Barreira para homens public synchronized void entrahomem (int id) { try { this.h++; while ((this.m == 0) (this.formandoparh == 1)) { wait(); //bloqueia pela condicao logica da aplicacao this.formandoparh = 1; //um homem foi selecionado para formar um par if(this.formandoparm == 0) { notifyall(); //desbloqueia as mulheres esperando par else { this.m--; this.h--; //um par foi formado this.formandoparm = 0; this.formandoparh = 0; //termina a formacao do par catch (InterruptedException e) {