Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.