Ir al contenido

Ejercicios de sincronización (los 32 oficiales)

Reglas comunes: usar pthread, un hilo por entidad, while(1) continuo, sleep() para simular tiempos, mensajes claros y nada de espera activa, salvo en el ejercicio del spinlock. Compilar con gcc x.c -o x -pthread.

Cómo decidir la primitiva:

  • Exclusión mutua de 1 (un recurso único): pthread_mutex_t, o un semáforo en 1.
  • K unidades de un recurso: un semáforo contador en K.
  • Orden o señal entre hilos (A antes que B): un semáforo en 0.
  • Esperar a que se cumpla una condición sobre variables compartidas (saldo ≥ x, contador < K, «todos terminaron»): mutex + condition variable con while.
  • Todos esperan a todos: una barrera (pthread_barrier_t, o contador + condición + broadcast).
#EjercicioRecursosSolución
1Impresora compartida (4 trabajadores)1 impresoramutex (o semáforo en 1) alrededor de imprimir
2Puente de un carril (5 vehículos)1 puentemutex alrededor de cruzar
3Almacén: 3 trabajadores agregan, 4 repartidores retiranpackagesmutex sobre packages + semáforo disponibles = 0 (post al agregar, wait antes de retirar) o condición while (packages == 0) wait
4Cafetería: 3 dentro, 1 caja3 lugares y 1 cajasemáforo aforo = 3 + mutex caja; en orden: aforo, caja, liberar caja, liberar aforo
5Restaurante: 5 mesas, 2 meseros, 1 caja5, 2, 1mesas = 5, meseros = 2, caja = 1. Se toma la mesa, luego el mesero (se libera tras la atención), luego la caja. La mesa se libera recién al pagar
6Lavandería: 3 lavadoras, 2 secadoras3, 2lavadoras = 3, secadoras = 2. Se libera la lavadora antes de pedir la secadora: así no hay hold and wait
7Hospital: 3 consultorios, 2 médicos, 1 rayos X3, 2, 1ver abajo: orden fijo consultorio → médico o rayos; al ir a rayos se libera el médico y se retiene el consultorio
8Barbería: 2 barberos, 5 sillas de esperasala acotadapatrón sleeping barber (abajo)
9Ascensor: capacidad 6, dos sentidossentido + cupomonitor tipo baño unisex con capacidad + turno contra la starvation (abajo)
Ejercicio 7 · Hospital (ejecución de prueba: sin violaciones, 8700–9500 atenciones en 3 s)
sem_t consultorio, medico, rayos; // = 3, 2, 1
void *paciente(void *arg) {
while (1) {
sem_wait(&consultorio); // siempre PRIMERO el consultorio
sem_wait(&medico);
evaluar();
if (necesita_rayos()) {
sem_post(&medico); // libera al médico, RETIENE el consultorio
sem_wait(&rayos); tomar_rayos(); sem_post(&rayos);
sem_wait(&medico); // reevaluación en SU consultorio
interpretar();
}
sem_post(&medico);
sem_post(&consultorio);
sleep(rand() % 3);
}
}

¿Por qué no hay deadlock? Quien espera un médico o los rayos ya tiene un consultorio, pero nadie que tenga un médico o los rayos espera un consultorio. El orden de adquisición es siempre consultorio → médico/rayos, así que no puede cerrarse un ciclo de espera. Un paciente con médico y consultorio siempre termina o suelta al médico.

Ejercicio 8 · Barbería (ejecución de prueba; el assert «≤ 2 atendiendo» detectó un bug en la primera versión)
#define SILLAS 5
pthread_mutex_t m; int esperando = 0;
sem_t clientes; // = 0: clientes esperando (despierta a los barberos)
sem_t barberos; // = 0: barberos que llaman a un cliente
sem_t cliente_listo, barbero_listo; // = 0: rendezvous de fin de corte
void *barbero(void *a) {
while (1) {
sem_wait(&clientes); // duerme si no hay clientes
pthread_mutex_lock(&m); esperando--; pthread_mutex_unlock(&m);
sem_post(&barberos); // llama a UN cliente
cortar_pelo();
sem_wait(&cliente_listo); // ¡espera a que termine ANTES de llamar a otro!
sem_post(&barbero_listo);
}
}
void *cliente(void *a) {
while (1) {
pthread_mutex_lock(&m);
if (esperando == SILLAS) { // sala llena: se va sin esperar
pthread_mutex_unlock(&m); printf("sala llena, me voy\n"); sleep(1); continue;
}
esperando++;
pthread_mutex_unlock(&m);
sem_post(&clientes); // "hay un cliente"
sem_wait(&barberos); // espera a que lo llamen (libera la silla)
recibir_corte();
sem_post(&cliente_listo); sem_wait(&barbero_listo);
sleep(rand() % 3);
}
}

El bug que atrapó el assert: sin el rendezvous cliente_listo / barbero_listo, un barbero hacía sem_post(&barberos) y volvía a llamar a otro cliente mientras el primero seguía cortándose, y aparecían 3 clientes siendo atendidos con 2 barberos. Con el rendezvous, cada barbero tiene a lo sumo un llamado pendiente, así que nunca hay más de 2 cortes simultáneos.

Ejercicio 9 · Ascensor (ejecución de prueba: capacidad respetada y ~3200 viajes en cada sentido, sin starvation)
#define CAP 6
enum { NONE = -1, SUBIR = 0, BAJAR = 1 };
pthread_mutex_t m; pthread_cond_t puede[2];
int dir = NONE, dentro = 0, esperan[2] = {0, 0}, turno = NONE;
void entrar(int d) {
int o = 1 - d;
pthread_mutex_lock(&m);
esperan[d]++;
while (!((dir == NONE || dir == d) && dentro < CAP && !(esperan[o] > 0 && turno == o)))
pthread_cond_wait(&puede[d], &m);
esperan[d]--;
dir = d; dentro++;
if (esperan[o] > 0) turno = o; // hay gente del otro lado esperando: el siguiente turno es suyo
pthread_mutex_unlock(&m);
}
void salir(void) {
pthread_mutex_lock(&m);
dentro--;
if (dentro == 0) { // vacío: puede cambiar de sentido
dir = NONE;
pthread_cond_broadcast(&puede[SUBIR]);
pthread_cond_broadcast(&puede[BAJAR]);
} else
pthread_cond_broadcast(&puede[dir]); // se liberó un cupo en este sentido
pthread_mutex_unlock(&m);
}

Es el baño unisex más la condición dentro < CAP. La condición turno impide que el sentido activo acapare el ascensor, porque cuando alguien espera del otro lado, los nuevos de este sentido se detienen.

#EjercicioPatrónPrimitivas
1Único finalizadorcontador protegido; quien lo deja en N imprimemutex + if (++hechos == N) printf(…)
2Capacidad Kmultiplexsemáforo = K
3Incremento atómicorace → atómicacounter++ pierde incrementos; __sync_fetch_and_add(&counter, 1) o atomic_fetch_add no
4Múltiples señales (B→C ×20, C→A)señalización repetidasem bc = 0 (B hace post ×20, C hace wait ×20) y sem ca = 0 (C hace post, A hace wait)
5A y B antes que Cdos señalessem s = 0; A y B hacen post; C hace wait ×2
6SpinlockTAS o CASwhile (__sync_lock_test_and_set(&lk, 1)); … __sync_lock_release(&lk);
7Turnos alternados A, B, A, B ×10ping-pongsa = 1, sb = 0; A: wait(sa) … post(sb); B: wait(sb) … post(sa)
8N trabajadores ×5 y un supervisorcontar notificacionessem n = 0; cada operación hace post; el supervisor hace wait 5N veces
9Dos etapasbarrerapthread_barrier_wait entre las etapas (N hilos)
10Transferencia A→B (A + B constante)la secuencia es la SCun mutex alrededor de A--; B++; (coherencia de datos)
11Espera por mayoría (N/2)contador + condiciónmutex + cond: while (hechos < N/2) wait; los trabajadores hacen hechos++; signal
12Cambio de fase A → B tras Kfase globalmutex + cond: while (fase == A && hechasA >= K) …; el que completa la K-ésima cambia la fase y hace broadcast
13N trabajadores y un observadoresperar a todospthread_join de los N, o sem = 0 con wait ×N
14Orden A→B→Ccadena de señalesab = 0, bc = 0; A: …; post(ab); B: wait(ab); …; post(bc); C: wait(bc)
15Dos parejas (A-B, C-D)rendezvous por parejaA: post(a); wait(b), B: post(b); wait(a); ídem con C-D
16Trabajo acumuladoacumular y esperarmutex en work += x; B espera a todos (join o semáforo ×N)
17Contador en [0, K]dos condicionesmutex + cond: while (counter == K) wait para incrementar y while (counter == 0) wait para decrementar
18Una señal B → Aseñalizaciónsem = 0: B hace post, A hace wait
19Reserva y confirmaciónrecurso contadosem available = K + mutex para reserved (o mutex + cond)
20Máximo concurrentelectura-comparación-escritura atómicamutex alrededor de if (x > max) max = x, o un bucle con CAS
21Tres etapas A→B→C (repetidas)pipelineab = 0, bc = 0, y para repetir, ca = 1 que controla a A
22Dos A por cada Bcontar las Asem countA = 0; A hace post; B hace dos wait. Con varios B, hay que proteger los dos wait con un mutex, o dos B pueden tomar una A cada uno y trabarse
23Último en llegarbarrera + «el último imprime»mutex + cond: if (++llegaron == N) { printf; broadcast; } else while (llegaron < N) wait;

La trampa del 22 (en tu e22.c): con N_A = 6 y N_B = 3 funciona porque sobran A. Pero si B1 y B2 hacen su primer wait a la vez y solo quedan 2 A, cada uno toma una y ambos esperan la segunda, que nunca llega si no hay más A: hay hold and wait. La solución es un mutex alrededor de los dos sem_wait, para que un B tome las dos A de forma atómica.

Fuente ·lab2/09-OSL-Ejercicios.pdf · lab2/Ejercicios_Repaso.pdf · código del curso so/sem4/ejercicios_repaso/e1.c … e23.c · prueba propia verif_parcial/ej/{barberia,ascensor,hospital}.c