Ir al contenido

El barbero dormilón (1 y N barberos)

🎯 Al terminar podrás

  1. Explicar por qué wait(seats) y sem_getvalue fallan, y por qué sem_trywait sí sirve (comprobar y tomar en una sola operación atómica).
  2. Escribir la solución de 1 barbero con 4 semáforos, sabiendo el valor inicial de cada uno y quién hace signal.
  3. Encontrar los errores del código de N barberos de las diapositivas y justificar si se puede quitar barber_chair.
  4. Discutir la justicia: barging y starvation con semáforos que no son FIFO.

Antes necesitas: Semáforos POSIX y sem_trywait · Productor–consumidor

  • Detective escribe tu hipótesis del bug antes de abrir el diagnóstico
  • Laboratorio sube la escalera: resuelto → completa → solo → variante
  • Socrático responde la pregunta guía antes de seguir leyendo

Las sillas de espera: tres intentos (diap. 34–37)

Sección titulada «Las sillas de espera: tres intentos (diap. 34–37)»
Intento 1: `semaphore seats = 5; wait(seats);` — «Does it work?»

No. wait bloquea si no hay sillas: el cliente se queda esperando en la puerta en lugar de irse, y la regla «si no hay silla, se va» no se cumple. Las colas de espera crecen sin límite.

Intento 3, correcto: un mutex + contador (diap. 35). La comprobación y el decremento quedan dentro de la misma sección crítica:

sem_t mutex; int seats = 5;
wait(mutex);
if (seats > 0) { seats--; signal(mutex); enters_barbershop(); }
else { signal(mutex); leaves_barbershop(); }

Más simple: sem_trywait (diap. 36–37). Decrementa solo si el valor es positivo; si no, retorna −1 con EAGAIN sin bloquear. Comprobar y tomar es una sola operación atómica:

semaphore seats = 5;
if (trywait(seats) == 0) enters_barbershop();
else leaves_barbershop(); // sin corte

«Los clientes solo esperan en las sillas.»

«All clients need to wait on the seats?»

Con el diseño de las diapositivas, sí: todo cliente toma primero una silla de espera (trywait(seats)) y desde ahí espera la silla del barbero (wait(barber_chair)). Si el barbero está libre, el cliente ocupa la silla de espera solo un instante: la toma y la suelta de inmediato.

La alternativa sería intentar primero la silla del barbero. Es la pregunta siguiente, y tiene un problema de justicia.

Diap. 38: «Barber chair dispute… Is it a fair approach?»
if (trywait(barber_chair) == 0) // ¿la silla del barbero está libre? me siento directo
...
else if (trywait(seats) == 0) // si no, busco silla de espera
wait(barber_chair);

No es justo. Un cliente que acaba de llegar puede encontrar la silla del barbero libre justo cuando se desocupa y adelantarse a los que llevan rato esperando (barging). Peor aún, los que esperan en wait(barber_chair) no tienen orden garantizado: los semáforos POSIX no son FIFO. Un cliente puede quedarse esperando indefinidamente mientras otros pasan: starvation.

Para ser justo, hace falta una cola FIFO explícita: números de turno como en Bakery, o un semáforo fuerte.

Diap. 39: `wait(barber_chair); signal(barber_chair);` — «And the other clients?»

Si el cliente libera la silla apenas la toma, la silla no queda reservada durante el corte: otro cliente puede tomarla y «sentarse» mientras el primero se está cortando el pelo. La silla debe retenerse hasta que el corte termina. ¿Y quién sabe cuándo termina? El barbero (siguiente punto).

El barbero. El cliente no puede saber cuándo terminó su corte, así que hace falta un semáforo más, hair_cut, que el barbero señala al terminar.

Solución completa con 1 barbero:

semaphore seats = 5, barber_chair = 1;
semaphore client_seat = 0, hair_cut = 0;
/* Cliente */ /* Barbero */
if (trywait(seats) == 0) { while (true) {
wait(barber_chair); // espero silla wait(client_seat); // duerme si no hay nadie
signal(seats); // libero espera cut_shave();
signal(client_seat); // «ya me senté» signal(hair_cut); // «terminé»
wait(hair_cut); // espero el corte }
signal(barber_chair);
} else leaves_barbershop();
SemáforoValor inicialPara qué
seats5sillas de espera
barber_chair1exclusión mutua sobre la silla del barbero
client_seat0señal «hay un cliente sentado»: el barbero duerme aquí
hair_cut0señal «corte terminado»: el cliente espera aquí

Ejecución de prueba (lo pide la diap. 41, «implement a solution barber.c»). 300 clientes llegando cada 0–120 µs, cortes de 0–300 µs, con un assert de que el barbero nunca corta a dos a la vez:

barberos=1: atendidos=237, se fueron=63 (total 300), máx esperando en sillas=5 (<= 5)

Nunca hubo más de 5 esperando, el assert no falló, y los 63 que no encontraron silla se fueron en lugar de bloquearse.

Peldaño 2 · Complétalofaltan pasos clave: llénalos tú

Complétalo: los 4 semáforos de la solución de 1 barbero

Sin mirar arriba, completa la tabla.

SemáforoValor inicial¿Quién hace wait?¿Quién hace signal?
seats???
barber_chair???
client_seat???
hair_cut???
Comparar
SemáforoValor inicialwaitsignal
seats5cliente (trywait)cliente, al pasar a la silla del barbero
barber_chair1clientecliente, al terminar el corte
client_seat0barbero (duerme aquí)cliente («ya me senté»)
hair_cut0clientebarbero («terminé»)

Regla mental: los semáforos de exclusión empiezan en la cantidad de recursos (5 y 1). Los de aviso empiezan en 0, y los señala el otro actor.

Peldaño 3 · Solosin ayuda; luego compara

Solo: el barbero completo en C (25 minutos)

1 barbero, 5 sillas y 40 clientes que llegan a intervalos aleatorios. Cuenta los atendidos y los que se fueron, y agrega un assert de que el barbero nunca corta a dos a la vez. Compila con gcc -Wall -pthread.

Una solución (probada: 30 de 30 ejecuciones terminaron sin fallar el assert)
#include <assert.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define SILLAS 5
#define CLIENTES 40
sem_t seats, barber_chair, client_seat, hair_cut, cuenta;
int atendidos = 0, se_fueron = 0, en_corte = 0;
void *barbero(void *arg) {
(void)arg;
while (1) {
sem_wait(&client_seat);
sem_wait(&cuenta);
en_corte++;
assert(en_corte == 1);
sem_post(&cuenta);
usleep(rand() % 300);
sem_wait(&cuenta);
en_corte--;
atendidos++;
sem_post(&cuenta);
sem_post(&hair_cut);
}
return NULL;
}
void *cliente(void *arg) {
(void)arg;
if (sem_trywait(&seats) == 0) {
sem_wait(&barber_chair);
sem_post(&seats);
sem_post(&client_seat);
sem_wait(&hair_cut);
sem_post(&barber_chair);
} else {
sem_wait(&cuenta);
se_fueron++;
sem_post(&cuenta);
}
return NULL;
}
int main(void) {
pthread_t b, c[CLIENTES];
sem_init(&seats, 0, SILLAS);
sem_init(&barber_chair, 0, 1);
sem_init(&client_seat, 0, 0);
sem_init(&hair_cut, 0, 0);
sem_init(&cuenta, 0, 1);
pthread_create(&b, NULL, barbero, NULL);
for (int i = 0; i < CLIENTES; i++) {
pthread_create(&c[i], NULL, cliente, NULL);
usleep(rand() % 100);
}
for (int i = 0; i < CLIENTES; i++) pthread_join(c[i], NULL);
printf("atendidos=%d se_fueron=%d total=%d\n", atendidos, se_fueron, atendidos + se_fueron);
return 0;
}

En una ejecución: atendidos=27 se_fueron=13 total=40. Los números cambian en cada corrida; lo que no puede cambiar es que total = 40 y que el assert nunca falle. El barbero queda en un while (1): el programa termina cuando main retorna tras el join de los clientes.

Ahora hay n barberos (n sillas de barbero) y m sillas de espera. El cliente necesita saber qué barbero lo atiende: es el problema de la cola del banco («pase a la ventanilla 3»). Idea de las diapositivas: un panel donde un barbero libre publica su id.

  • Los barberos escriben el panel, uno a la vez, y solo cuando el id anterior ya fue leído.
  • Los clientes leen el panel, uno por cada id publicado.
semaphore write_panel = 1, read_panel = 0;
int panel;

Versión corregida (propia):

semaphore seats = M, write_panel = 1, read_panel = 0;
semaphore client_seat[N] = {0…}, hair_cut[N] = {0…};
int panel;
/* Barbero id */ /* Cliente */
while (true) { if (trywait(seats) == 0) {
wait(write_panel); // ¿panel libre? wait(read_panel); // espero un id
panel = id; // «estoy libre» int my = panel; // lo leo
signal(read_panel); // que un cliente lo lea signal(write_panel); // panel libre otra vez
wait(client_seat[id]); signal(seats); // dejo la silla de espera
cut_shave(); signal(client_seat[my]);
signal(hair_cut[id]); wait(hair_cut[my]);
} } else leaves_barbershop();

Ejecución de prueba (300 clientes, 5 sillas de espera, assert de que ningún barbero corta a dos a la vez):

barberos=2: atendidos=299, se fueron=1 (total 300), máx esperando en sillas=5 (<= 5)
barberos=3: atendidos=300, se fueron=0 (total 300), máx esperando en sillas=2 (<= 5)
barberos=4: atendidos=300, se fueron=0 (total 300), máx esperando en sillas=1 (<= 5)

Con más barberos se van menos clientes y la sala de espera se usa menos, como era de esperar.

Tarea (diap. 47): «Discuss and evaluate if we can remove the barber_chair»

Sí, se puede quitar. La versión corregida de arriba no tiene barber_chair y la prueba con 2, 3 y 4 barberos no violó nunca «un cliente por barbero».

Por qué: la exclusión sobre la silla ya la garantiza el panel. Un barbero solo publica su id cuando está libre: lo hace al inicio de cada vuelta del while, después de terminar el corte anterior. Cada publicación la consume exactamente un cliente, porque read_panel vale 1 por cada id publicado y write_panel impide publicar otro antes de que se lea. Por lo tanto, nunca hay dos clientes con el mismo my al mismo tiempo, y barber_chair[my] sería un semáforo que siempre se toma sin esperar.

Con 1 barbero, en cambio, barber_chair sí hace falta. Sin él, dos clientes podrían hacer signal(client_seat) y quedar los dos en wait(hair_cut). Cuando el barbero termina el primer corte, signal(hair_cut) puede despertar al cliente equivocado, porque el semáforo no es FIFO: alguien se iría sin terminar su corte. En la versión de N barberos ese papel lo cumple el panel.

El mismo problema, con monitores: el ejercicio 8 · Barbería (2 barberos, 5 sillas) se resolvió con mutex + condition variables, y su assert detectó un bug en la primera versión.

Autoevaluación

0/5 correctas · 0 respondidas
1. ¿Por qué wait(seats) no cumple «si no hay silla, el cliente se va»?
2. if (sem_getvalue(seats) > 0) wait(seats); falla porque…
3. sem_trywait con el semáforo en 0…
4. ¿Quién señala hair_cut?
5. Con N barberos y panel, ¿se puede quitar barber_chair?

📈 ¿Qué tan bien lo dominas?

Elige pensando en lo que hiciste, no en lo que leíste. Para marcar 3 tienes que haber resuelto un ejercicio nuevo sin pistas y haberlo justificado. Si solo lo leíste y sientes que «ya entendí», todavía es un 1.

Fuente ·sem_9/11-OSL-Deadlock.pdf (diap. 32–47) · man sem_trywait(3) · Pruebas propias: barber.c (1–4 barberos) y barber_slides.c (código de las diapositivas transcrito), ejecuciones de prueba