El barbero dormilón (1 y N barberos)
🎯 Al terminar podrás
- Explicar por qué
wait(seats)ysem_getvaluefallan, y por quésem_trywaitsí sirve (comprobar y tomar en una sola operación atómica). - Escribir la solución de 1 barbero con 4 semáforos, sabiendo el valor inicial de cada uno y quién hace
signal. - Encontrar los errores del código de N barberos de las diapositivas y justificar si se puede quitar
barber_chair. - 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.»
La silla del barbero (diap. 37–39)
Sección titulada «La silla del barbero (diap. 37–39)»«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).
¿Quién decide terminar? (diap. 40–41)
Sección titulada «¿Quién decide terminar? (diap. 40–41)»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áforo | Valor inicial | Para qué |
|---|---|---|
seats | 5 | sillas de espera |
barber_chair | 1 | exclusión mutua sobre la silla del barbero |
client_seat | 0 | señal «hay un cliente sentado»: el barbero duerme aquí |
hair_cut | 0 | señ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áforo | Valor inicial | ¿Quién hace wait? | ¿Quién hace signal? |
|---|---|---|---|
seats | ? | ? | ? |
barber_chair | ? | ? | ? |
client_seat | ? | ? | ? |
hair_cut | ? | ? | ? |
Comparar
| Semáforo | Valor inicial | wait | signal |
|---|---|---|---|
seats | 5 | cliente (trywait) | cliente, al pasar a la silla del barbero |
barber_chair | 1 | cliente | cliente, al terminar el corte |
client_seat | 0 | barbero (duerme aquí) | cliente («ya me senté») |
hair_cut | 0 | cliente | barbero («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.
N barberos (diap. 42–47)
Sección titulada «N barberos (diap. 42–47)»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 respondidaswait(seats) no cumple «si no hay silla, el cliente se va»?if (sem_getvalue(seats) > 0) wait(seats); falla porque…hair_cut?📈 ¿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