Sleeping Barber
Uma barbearia tem um barbeiro, uma cadeira de corte e algumas cadeiras de espera. Sem cliente, o barbeiro dorme. Quando um cliente chega, se o barbeiro está dormindo, ele o acorda e é atendido; se o barbeiro está ocupado, o cliente senta pra esperar, desde que haja cadeira livre; se não há, ele vai embora. O desafio é que o barbeiro dormir e ser acordado é precisamente uma comunicação entre threads: uma dorme, outra a desperta.
Esse é o uso do semáforo que separa ele de um mutex. Lá no capítulo de semáforos, o
ponto era que semáforo não tem dono: qualquer thread pode fazer sem_post, não só
quem fez sem_wait. Aqui isso deixa de ser curiosidade e vira o mecanismo central. O
barbeiro bloqueia num semáforo clientes, que começa em zero, e é outra thread, o
cliente, que faz sem_post nele. O barbeiro dorme; o cliente o acorda. Um mutex não
faz isso, porque o barbeiro não poderia liberar um lock que o cliente adquiriu.
O barbeiro, os clientes e dois semáforos
A conversa acontece em dois sentidos, então há dois semáforos. clientes é o cliente
avisando o barbeiro “cheguei”; barbeiro é o barbeiro avisando o cliente “pode vir”.
Além deles, um mutex protege a contagem de quem está esperando, porque decidir se há
cadeira livre é um teste-e-ação sobre um contador compartilhado, e dois clientes
chegando juntos não podem, ambos, achar que pegaram a última cadeira.
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#define CADEIRAS 3
sem_t clientes; // clientes esperando; o barbeiro dorme aqui
sem_t barbeiro; // o barbeiro sinaliza que vai atender
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
int esperando = 0; // clientes na sala de espera
int fechado = 0; // a barbearia fechou
void *o_barbeiro(void *arg) {
for (;;) {
sem_wait(&clientes); // dorme se não tem ninguém
pthread_mutex_lock(&mtx);
if (fechado && esperando == 0) { pthread_mutex_unlock(&mtx); break; }
esperando--;
pthread_mutex_unlock(&mtx);
sem_post(&barbeiro); // chama o próximo
printf("barbeiro cortando cabelo\n");
usleep(30000);
}
printf("barbeiro foi pra casa\n");
return NULL;
}
void *cliente(void *arg) {
int id = *(int *)arg;
pthread_mutex_lock(&mtx);
if (esperando < CADEIRAS) {
esperando++;
printf("cliente %d senta pra esperar (%d na fila)\n", id, esperando);
pthread_mutex_unlock(&mtx);
sem_post(&clientes); // acorda o barbeiro
sem_wait(&barbeiro); // espera ser chamado
} else {
printf("cliente %d foi embora, sala cheia\n", id);
pthread_mutex_unlock(&mtx);
}
return NULL;
}
int main(void) {
setbuf(stdout, NULL);
sem_init(&clientes, 0, 0);
sem_init(&barbeiro, 0, 0);
pthread_t b, c[6]; int id[6];
pthread_create(&b, NULL, o_barbeiro, NULL);
for (int i = 0; i < 6; i++) { id[i]=i; pthread_create(&c[i],NULL,cliente,&id[i]); usleep(10000); }
for (int i = 0; i < 6; i++) pthread_join(c[i],NULL);
pthread_mutex_lock(&mtx); fechado = 1; pthread_mutex_unlock(&mtx);
sem_post(&clientes); // acorda o barbeiro pra ele fechar
pthread_join(b, NULL);
return 0;
}
Código 10-5: Barbeiro e clientes coordenados por dois semáforos e um mutex.
$ cc main.c -o main -pthread
$ ./main
cliente 0 senta pra esperar (1 na fila)
barbeiro cortando cabelo
cliente 1 senta pra esperar (1 na fila)
cliente 2 senta pra esperar (2 na fila)
barbeiro cortando cabelo
cliente 3 senta pra esperar (2 na fila)
cliente 4 senta pra esperar (3 na fila)
cliente 5 foi embora, sala cheia
barbeiro cortando cabelo
barbeiro cortando cabelo
barbeiro cortando cabelo
barbeiro foi pra casa
O cliente 5 chega com a sala já cheia e vai embora sem esperar, exatamente o
comportamento que o enunciado pede. Os outros cinco são atendidos, cada um acordando
o barbeiro com um sem_post(&clientes) e esperando a vez com um sem_wait(&barbeiro).
A dança de dormir e acordar, que num mutex seria impossível, aqui é só um semáforo
que uma thread espera e outra libera.
O fechado no fim resolve o mesmo problema de término do produtor-consumidor por
outro caminho: sem ele, o barbeiro ficaria pra sempre dormindo no sem_wait,
esperando um cliente que não vem mais. A main, depois que todos os clientes foram
atendidos, marca a barbearia como fechada e faz um último sem_post para acordar o
barbeiro uma vez mais, só pra ele ver que o expediente acabou e ir pra casa.