Semana 11 — Variables de condición y semáforos
Objetivos
- Usar variables de condición (condition variables) correctamente: siempre con lock y siempre en un
while. - Resolver productor/consumidor, lectores/escritores y los filósofos comensales (dining philosophers) con semáforos.
Sesión A — Variables de condición
Lecturas:
- Cap. 30 Condition Variables · código
- Apéndice opcional: Monitors
Temas:
waitysignal; por quéwaitlibera el lock.- Productor/consumidor (bounded buffer) paso a paso: el error del
if, el error de una sola CV. - Semántica Mesa vs. Hoare. Covering conditions (
broadcast).
Sesión B — Semáforos
Lecturas:
- Cap. 31 Semaphores · código
Temas:
- Semáforo como lock (binario) y como variable de condición.
- Productor/consumidor con semáforos y dónde va el mutex.
- Lock de lectores/escritores. Filósofos comensales. Throttling.
- Implementar semáforos con locks y CVs (Zemaphores).
- Inversión de prioridad: el caso del Mars Pathfinder; herencia y techo de prioridad;
PTHREAD_PRIO_INHERITen Linux.
Lecturas complementarias (inversión de prioridad; OSTEP no la cubre):
- Mike Jones, What really happened on Mars? (1997), y la respuesta de Glenn Reeves, responsable del software de vuelo.
- Páginas man:
pthread_mutexattr_setprotocolysched(7)(SCHED_FIFO). - Documentación del kernel de Linux: RT-mutex y PI futex.
- Opcional: Tanenbaum y Bos, Modern Operating Systems, 5.ª ed., sec. 2.4.10 Priority Inversion.
Laboratorio — Sincronización
main-*.cdel cap. 30: ejecutar cada variante del productor/consumidor y explicar por qué falla o funciona.- Tarea de código del cap. 31: barrera (barrier), rendezvous, lectores/escritores sin inanición, mutex sin inanición.
Entregas
- Bono publicado:
forkcon copy-on-write en xv6 (labcowdel MIT). Entrega en semana 16.
Preguntas de repaso
- Muestre una intercalación en la que usar
ifen lugar dewhilealrededor dewaitrompe el productor/consumidor. - ¿A qué valor se inicializa un semáforo usado como lock? ¿Y uno usado para esperar a que un hilo termine?
- ¿Por qué la solución ingenua de los filósofos produce deadlock y cómo se evita?
- ¿Qué es la inversión de prioridad? ¿Por qué la herencia de prioridad acota la espera del hilo de prioridad alta?
Código de la semana
Compilar dentro del contenedor del curso con make o gcc -Wall.
Ejemplos (8)Errores (1)Compilación (1)
Ejemplos
Los programas que usan los apuntes y la clase.
// join.c — el padre espera al hijo con una variable de condición.
//
// Uso: ./join ok estado + lock + while: funciona siempre
// ./join nostate sin la variable done: si el hijo avisa antes,
// el aviso se pierde y el padre duerme para siempre
// ./join nolock el padre revisa done sin el lock: el hijo avisa
// justo entre la revisión y el wait
// Los retrasos (msleep) fuerzan la intercalación mala; un watchdog
// detecta que el padre no despierta.
#include <string.h>
#include "trace.h"
static pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t c = PTHREAD_COND_INITIALIZER;
static int done = 0;
static const char *mode;
static void thr_exit(void) {
if (strcmp(mode, "nolock") == 0) {
done = 1; // sin lock
TRACE("hijo: done = 1, signal (sin lock)");
pthread_cond_signal(&c);
} else {
pthread_mutex_lock(&m);
if (strcmp(mode, "ok") == 0)
done = 1;
TRACE("hijo: %ssignal", strcmp(mode, "ok") == 0 ? "done = 1, " : "");
pthread_cond_signal(&c);
pthread_mutex_unlock(&m);
}
}
static void thr_join(void) {
if (strcmp(mode, "ok") == 0) {
pthread_mutex_lock(&m);
while (done == 0) {
TRACE("padre: done == 0, wait");
pthread_cond_wait(&c, &m);
}
pthread_mutex_unlock(&m);
} else if (strcmp(mode, "nostate") == 0) {
pthread_mutex_lock(&m);
TRACE("padre: wait (no hay estado que revisar)");
pthread_cond_wait(&c, &m);
pthread_mutex_unlock(&m);
} else { // nolock
if (done == 0) { // revisa sin el lock...
TRACE("padre: done == 0 (revisado sin lock)");
msleep(100); // ...y lo desalojan aquí
pthread_mutex_lock(&m);
TRACE("padre: wait");
pthread_cond_wait(&c, &m);
pthread_mutex_unlock(&m);
}
}
}
static void *child(void *arg) {
(void)arg;
if (strcmp(mode, "nolock") == 0)
msleep(50); // llega en medio del padre
TRACE("hijo: trabajo terminado");
thr_exit();
return NULL;
}
int main(int argc, char *argv[]) {
mode = argc > 1 ? argv[1] : "ok";
if (strcmp(mode, "ok") && strcmp(mode, "nostate") && strcmp(mode, "nolock")) {
fprintf(stderr, "uso: %s ok|nostate|nolock\n", argv[0]);
return 1;
}
trace_start();
watchdog_start(1, "El padre duerme: el aviso se perdió.", NULL);
pthread_t p;
pthread_create(&p, NULL, child, NULL);
if (strcmp(mode, "nostate") == 0)
msleep(50); // el hijo corre primero
thr_join();
TRACE("padre: el hijo terminó");
watchdog_done();
pthread_join(p, NULL);
return 0;
}// pc.c — productor/consumidor (bounded buffer) con dos variables de condición.
//
// Uso: ./pc MAX P C N
// MAX tamaño del buffer P productores
// C consumidores N elementos por productor
// Cada productor mete N números; los consumidores los sacan hasta recibir
// un -1 (fin). Al final se comprueba que la suma de lo consumido sea la
// de lo producido.
#include "trace.h"
static int *buffer;
static int max;
static int fill_ptr = 0, use_ptr = 0, count = 0;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t empty = PTHREAD_COND_INITIALIZER; // hay espacio
static pthread_cond_t fill = PTHREAD_COND_INITIALIZER; // hay datos
static void put(int value) {
buffer[fill_ptr] = value;
fill_ptr = (fill_ptr + 1) % max;
count++;
}
static int get(void) {
int tmp = buffer[use_ptr];
use_ptr = (use_ptr + 1) % max;
count--;
return tmp;
}
static void produce(int value) {
pthread_mutex_lock(&mutex);
while (count == max) // lleno: esperar espacio
pthread_cond_wait(&empty, &mutex);
put(value);
pthread_cond_signal(&fill); // despierta a un consumidor
pthread_mutex_unlock(&mutex);
}
static int consume(void) {
pthread_mutex_lock(&mutex);
while (count == 0) // vacío: esperar datos
pthread_cond_wait(&fill, &mutex);
int tmp = get();
pthread_cond_signal(&empty); // despierta a un productor
pthread_mutex_unlock(&mutex);
return tmp;
}
static int nitems;
static void *producer(void *arg) {
long id = (long)arg;
for (int i = 0; i < nitems; i++)
produce((int)(id * nitems + i) % 1000);
return NULL;
}
static void *consumer(void *arg) {
long *sum = arg;
for (;;) {
int v = consume();
if (v == -1)
break;
*sum += v;
}
return NULL;
}
int main(int argc, char *argv[]) {
if (argc != 5) {
fprintf(stderr, "uso: %s MAX P C N\n", argv[0]);
return 1;
}
max = atoi(argv[1]);
int np = atoi(argv[2]), nc = atoi(argv[3]);
nitems = atoi(argv[4]);
buffer = malloc(sizeof(int) * max);
pthread_t prod[np], cons[nc];
long sums[nc];
for (long i = 0; i < np; i++)
pthread_create(&prod[i], NULL, producer, (void *)i);
for (int i = 0; i < nc; i++) {
sums[i] = 0;
pthread_create(&cons[i], NULL, consumer, &sums[i]);
}
for (int i = 0; i < np; i++)
pthread_join(prod[i], NULL);
for (int i = 0; i < nc; i++)
produce(-1); // un fin por consumidor
long expected = 0, got = 0;
for (long id = 0; id < np; id++)
for (int i = 0; i < nitems; i++)
expected += (id * nitems + i) % 1000;
for (int i = 0; i < nc; i++) {
pthread_join(cons[i], NULL);
printf("consumidor %d: suma %ld\n", i, sums[i]);
got += sums[i];
}
printf("esperado %ld, obtenido %ld: %s\n", expected, got,
expected == got ? "OK" : "ERROR");
free(buffer);
return expected == got ? 0 : 1;
}// philo.c — los filósofos comensales con semáforos.
//
// Uso: ./philo naive todos toman primero el tenedor izquierdo: deadlock
// ./philo ordered el filósofo 4 toma primero el derecho: sin ciclo
// Cada filósofo come MEALS veces. Un watchdog detecta el deadlock y
// muestra qué tenedor tiene y cuál espera cada filósofo.
#include <semaphore.h>
#include <string.h>
#include "trace.h"
#define N 5
#define MEALS 1000
static sem_t forks[N];
static int ordered;
static atomic_int holding[N]; // tenedor que tiene (-1: ninguno)
static atomic_int waiting[N]; // tenedor que espera (-1: ninguno)
static int meals[N];
static int left(int p) { return p; }
static int right(int p) { return (p + 1) % N; }
static void take(int p, int f) {
atomic_store(&waiting[p], f);
sem_wait(&forks[f]);
atomic_store(&waiting[p], -1);
}
static void get_forks(int p) {
int first = left(p), second = right(p);
if (ordered && p == N - 1) { // el último rompe el ciclo
first = right(p);
second = left(p);
}
take(p, first);
atomic_store(&holding[p], first);
if (meals[p] == 0)
msleep(20); // la primera vez, todos a la vez
take(p, second);
}
static void put_forks(int p) {
sem_post(&forks[left(p)]);
sem_post(&forks[right(p)]);
atomic_store(&holding[p], -1);
}
static void *philosopher(void *arg) {
int p = (int)(long)arg;
for (int i = 0; i < MEALS; i++) {
get_forks(p);
meals[p]++; // comer
progress_bump();
put_forks(p);
}
return NULL;
}
static void report(void) {
for (int p = 0; p < N; p++)
printf(" filósofo %d: tiene el tenedor %d, espera el %d\n", p,
atomic_load(&holding[p]), atomic_load(&waiting[p]));
}
int main(int argc, char *argv[]) {
const char *mode = argc > 1 ? argv[1] : "";
if (strcmp(mode, "naive") && strcmp(mode, "ordered")) {
fprintf(stderr, "uso: %s naive|ordered\n", argv[0]);
return 1;
}
ordered = strcmp(mode, "ordered") == 0;
for (int i = 0; i < N; i++) {
sem_init(&forks[i], 0, 1);
holding[i] = waiting[i] = -1;
}
trace_start();
watchdog_start(1, "Deadlock: espera circular.", report);
pthread_t t[N];
for (long i = 0; i < N; i++)
pthread_create(&t[i], NULL, philosopher, (void *)i);
for (int i = 0; i < N; i++)
pthread_join(t[i], NULL);
watchdog_done();
TRACE("todos comieron %d veces: sin deadlock", MEALS);
return 0;
}// prioinv.c — inversión de prioridad real, con pthreads y SCHED_FIFO.
//
// Uso: ./prioinv none mutex normal: M retrasa a H
// ./prioinv inherit PTHREAD_PRIO_INHERIT: L hereda la prioridad de H
// Todos los hilos corren en la CPU 0 con SCHED_FIFO: L (10), M (20), H (30);
// main, con prioridad 40, solo los lanza. L toma el lock y lo usa 50 ms de
// CPU; H llega y pide el lock; M llega y usa 200 ms de CPU sin tocar el lock.
// SCHED_FIFO requiere privilegios (CAP_SYS_NICE): sin ellos, el programa lo
// avisa y termina. env/run.sh lanza el contenedor del curso con ese permiso:
// env/run.sh sh -c 'cd code/week-11 && make prioinv && ./prioinv none && ./prioinv inherit'
// En un Linux propio: sudo ./prioinv none
#define _GNU_SOURCE
#include <errno.h>
#include <sched.h>
#include <string.h>
#include "trace.h"
static pthread_mutex_t m;
static long h_asked;
// Gasta `ms` milisegundos de CPU de este hilo (no de reloj: si lo desalojan,
// el tiempo que no corre no cuenta).
static void burn(int ms) {
struct timespec a, b;
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &a);
do
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &b);
while ((b.tv_sec - a.tv_sec) * 1000 + (b.tv_nsec - a.tv_nsec) / 1000000 < ms);
}
static void *low(void *arg) {
(void)arg;
pthread_mutex_lock(&m);
TRACE("L (10): toma el lock");
burn(50); // sección crítica larga
TRACE("L (10): suelta el lock");
pthread_mutex_unlock(&m);
return NULL;
}
static void *medium(void *arg) {
(void)arg;
TRACE("M (20): empieza (no usa el lock)");
burn(200);
TRACE("M (20): termina");
return NULL;
}
static void *high(void *arg) {
(void)arg;
h_asked = now_ms();
TRACE("H (30): pide el lock");
pthread_mutex_lock(&m);
TRACE("H (30): obtiene el lock, esperó %ld ms", now_ms() - h_asked);
burn(5);
pthread_mutex_unlock(&m);
return NULL;
}
// Crea un hilo SCHED_FIFO con la prioridad dada.
static pthread_t spawn(void *(*fn)(void *), int prio) {
pthread_attr_t a;
struct sched_param sp = {.sched_priority = prio};
pthread_attr_init(&a);
pthread_attr_setinheritsched(&a, PTHREAD_EXPLICIT_SCHED);
pthread_attr_setschedpolicy(&a, SCHED_FIFO);
pthread_attr_setschedparam(&a, &sp);
pthread_t t;
int e = pthread_create(&t, &a, fn, NULL);
if (e) {
fprintf(stderr, "pthread_create: %s\n", strerror(e));
exit(1);
}
pthread_attr_destroy(&a);
return t;
}
int main(int argc, char *argv[]) {
const char *mode = argc > 1 ? argv[1] : "";
int proto;
if (!strcmp(mode, "none"))
proto = PTHREAD_PRIO_NONE;
else if (!strcmp(mode, "inherit"))
proto = PTHREAD_PRIO_INHERIT;
else {
fprintf(stderr, "uso: %s none|inherit\n", argv[0]);
return 1;
}
// Una sola CPU: así M de verdad le quita la CPU a L.
cpu_set_t one;
CPU_ZERO(&one);
CPU_SET(0, &one);
if (sched_setaffinity(0, sizeof one, &one) != 0) {
perror("sched_setaffinity");
return 1;
}
struct sched_param sp = {.sched_priority = 40};
if (sched_setscheduler(0, SCHED_FIFO, &sp) != 0) {
printf("%s: SCHED_FIFO no permitido (%s).\n", mode, strerror(errno));
printf(" Hace falta CAP_SYS_NICE: docker run --cap-add=SYS_NICE ... o sudo.\n");
printf(" Sin privilegios, ./priosim %s muestra lo mismo simulado.\n", mode);
return 0;
}
pthread_mutexattr_t ma;
pthread_mutexattr_init(&ma);
pthread_mutexattr_setprotocol(&ma, proto); // NONE o INHERIT
pthread_mutex_init(&m, &ma);
printf("protocolo: %s\n", mode);
trace_start();
pthread_t l = spawn(low, 10);
msleep(10); // L ya tiene el lock
pthread_t h = spawn(high, 30);
msleep(1); // H ya se bloqueó
pthread_t mm = spawn(medium, 20);
pthread_join(h, NULL);
pthread_join(mm, NULL);
pthread_join(l, NULL);
return 0;
}// priosim.c — simulación determinista de la inversión de prioridad.
//
// Uso: ./priosim none sin protocolo: M retrasa a H sin tocar el lock
// ./priosim inherit herencia de prioridad (priority inheritance)
// ./priosim ceiling techo de prioridad (priority ceiling)
// Una CPU, prioridades fijas y desalojo: siempre corre el hilo listo de
// mayor prioridad (como SCHED_FIFO). El tiempo avanza en ticks; cada hilo
// sigue un guion de pasos: RUN n (usar la CPU n ticks), LOCK y UNLOCK.
// No necesita privilegios y la salida es siempre la misma.
#include <stdio.h>
#include <string.h>
enum { RUN, LOCK, UNLOCK, END };
struct step { int op, n; };
struct thread {
const char *name;
int base, prio; // prioridad base y efectiva
int arrive; // tick de llegada
const struct step *prog;
int pc, left; // paso actual y ticks que le faltan
int blocked, done, since; // since: desde cuándo espera la CPU
char row[64]; // la línea de tiempo, un carácter por tick
};
// L usa el lock 4 ticks; H lo necesita 2 ticks; M nunca lo toca.
static const struct step progL[] = {{RUN, 1}, {LOCK, 0}, {RUN, 4}, {UNLOCK, 0}, {RUN, 1}, {END, 0}};
static const struct step progM[] = {{RUN, 6}, {END, 0}};
static const struct step progH[] = {{RUN, 1}, {LOCK, 0}, {RUN, 2}, {UNLOCK, 0}, {RUN, 1}, {END, 0}};
#define NT 3
#define CEIL 30 // techo del lock: la prioridad de H
static struct thread th[NT] = {
{.name = "H", .base = 30, .prio = 30, .arrive = 2, .prog = progH},
{.name = "M", .base = 20, .prio = 20, .arrive = 3, .prog = progM},
{.name = "L", .base = 10, .prio = 10, .arrive = 0, .prog = progL},
};
static struct thread *H = &th[0];
static struct thread *owner; // owner del lock (NULL: libre)
static int mode; // 0 none, 1 inherit, 2 ceiling
static int h_asked;
// Recalcula la prioridad del owner según el protocolo.
static void boost_owner(int t) {
if (!owner)
return;
int p = owner->base;
if (mode == 2)
p = CEIL; // techo: apenas toma el lock
if (mode == 1) // herencia: la del que más espera
for (int i = 0; i < NT; i++)
if (th[i].blocked && th[i].prio > p)
p = th[i].prio;
if (p != owner->prio) {
printf("t=%2d %s sube a prioridad %d\n", t, owner->name, p);
owner->prio = p;
}
}
static void do_unlock(struct thread *me, int t) {
owner = NULL;
printf("t=%2d %s suelta el lock\n", t, me->name);
if (me->prio != me->base) {
printf("t=%2d %s vuelve a prioridad %d\n", t, me->name, me->base);
me->prio = me->base;
}
struct thread *next = NULL; // el lock pasa al bloqueado de mayor prioridad
for (int i = 0; i < NT; i++)
if (th[i].blocked && (!next || th[i].prio > next->prio))
next = &th[i];
if (next) {
next->blocked = 0;
next->since = t;
next->pc++; // su LOCK ya se cumplió
owner = next;
printf("t=%2d %s obtiene el lock", t, next->name);
if (next == H)
printf(" (esperó %d ticks)", t - h_asked);
printf("\n");
boost_owner(t);
}
}
// Ejecuta los pasos instantáneos al frente del guion de `me`, en el instante t.
// Devuelve 1 si quedó listo para usar la CPU; 0 si se bloqueó o terminó.
static int instant_steps(struct thread *me, int t) {
for (;; me->pc++) {
const struct step *s = &me->prog[me->pc];
if (s->op == RUN) {
if (me->left == 0)
me->left = s->n;
return 1;
}
if (s->op == END) {
me->done = 1;
printf("t=%2d %s termina\n", t, me->name);
return 0;
}
if (s->op == UNLOCK) {
do_unlock(me, t);
continue;
}
// LOCK
if (me == H)
h_asked = t;
if (owner) {
me->blocked = 1;
printf("t=%2d %s pide el lock: lo tiene %s, se bloquea\n", t, me->name, owner->name);
boost_owner(t);
return 0;
}
owner = me;
printf("t=%2d %s toma el lock\n", t, me->name);
boost_owner(t);
}
}
// ¿Debe correr a antes que b? Mayor prioridad; si empatan, sigue el que
// corría (cur) y si no, el que espera hace más tiempo.
static int before(struct thread *a, struct thread *b, struct thread *cur) {
if (!b || a->prio != b->prio)
return !b || a->prio > b->prio;
if (a == cur || b == cur)
return a == cur;
return a->since < b->since;
}
int main(int argc, char *argv[]) {
const char *m = argc > 1 ? argv[1] : "";
if (!strcmp(m, "none"))
mode = 0;
else if (!strcmp(m, "inherit"))
mode = 1;
else if (!strcmp(m, "ceiling"))
mode = 2;
else {
fprintf(stderr, "uso: %s none|inherit|ceiling\n", argv[0]);
return 1;
}
struct thread *cur = NULL;
int t;
for (t = 0; t < 60; t++) {
for (int i = 0; i < NT; i++)
if (th[i].arrive == t) {
th[i].since = t;
printf("t=%2d %s llega\n", t, th[i].name);
}
struct thread *pick;
do { // elegir; si el elegido se bloquea, otra vez
pick = NULL;
for (int i = 0; i < NT; i++)
if (th[i].arrive <= t && !th[i].done && !th[i].blocked &&
before(&th[i], pick, cur))
pick = &th[i];
} while (pick && !instant_steps(pick, t));
if (!pick)
break; // todos terminaron
if (cur && cur != pick) {
if (!cur->done && !cur->blocked) {
printf("t=%2d %s desaloja a %s\n", t, pick->name, cur->name);
cur->since = t;
} else {
printf("t=%2d la CPU pasa a %s\n", t, pick->name);
}
}
cur = pick;
for (int i = 0; i < NT; i++) { // dibujar el tick t
struct thread *x = &th[i];
char c = ' ';
if (x == pick)
c = owner == x ? 'X' : '#';
else if (x->arrive <= t && !x->done)
c = x->blocked ? '-' : '.';
x->row[t] = c;
}
if (--pick->left == 0) { // terminó este RUN
pick->pc++;
if (pick->prog[pick->pc].op != LOCK)
instant_steps(pick, t + 1); // UNLOCK o END: al final del tick
}
}
printf("\n ");
for (int i = 0; i < t; i++)
putchar('0' + i % 10);
printf("\n");
for (int i = 0; i < NT; i++)
printf("%s (%d) %.*s\n", th[i].name, th[i].base, t, th[i].row);
printf("X: corre con el lock #: corre .: listo -: bloqueado en el lock\n");
return 0;
}// sempc.c — productor/consumidor con semáforos POSIX.
//
// Uso: ./sempc ok mutex DENTRO de empty/full: funciona
// ./sempc outside mutex AFUERA: el consumidor duerme con el mutex
// tomado y el productor no puede entrar (deadlock)
// Buffer de MAX elementos, 2 productores y 2 consumidores.
#include <semaphore.h>
#include <string.h>
#include "trace.h"
#define MAX 4
#define ITEMS 50000 // por productor
#define NP 2
#define NC 2
static int buffer[MAX];
static int fill_ptr = 0, use_ptr = 0;
static sem_t empty, full, mutex;
static int outside;
static void put(int v) { buffer[fill_ptr] = v; fill_ptr = (fill_ptr + 1) % MAX; }
static int get(void) { int v = buffer[use_ptr]; use_ptr = (use_ptr + 1) % MAX; return v; }
static void produce(int v) {
if (outside) {
sem_wait(&mutex); // MAL: el mutex primero
sem_wait(&empty);
} else {
sem_wait(&empty); // esperar un espacio libre
sem_wait(&mutex); // luego, exclusión mutua
}
put(v);
sem_post(&mutex);
sem_post(&full); // un dato más
}
static int consume(const char *who) {
if (outside) {
sem_wait(&mutex);
TRACE("%s: tiene el mutex, sem_wait(&full)", who);
sem_wait(&full); // duerme CON el mutex
} else {
sem_wait(&full);
sem_wait(&mutex);
}
int v = get();
sem_post(&mutex);
sem_post(&empty);
return v;
}
static void *producer(void *arg) {
long id = (long)arg;
if (outside) {
msleep(50); // el consumidor llega primero
TRACE("P%ld: sem_wait(&mutex)", id);
}
for (int i = 0; i < ITEMS; i++) {
produce(1);
progress_bump();
}
return NULL;
}
static long sums[NC];
static void *consumer(void *arg) {
long id = (long)arg;
char who[8];
snprintf(who, sizeof who, "C%ld", id);
for (;;) {
int v = consume(who);
if (v == -1)
return NULL;
sums[id] += v;
progress_bump();
}
}
int main(int argc, char *argv[]) {
const char *mode = argc > 1 ? argv[1] : "";
if (strcmp(mode, "ok") && strcmp(mode, "outside")) {
fprintf(stderr, "uso: %s ok|outside\n", argv[0]);
return 1;
}
outside = strcmp(mode, "outside") == 0;
sem_init(&empty, 0, MAX); // MAX espacios libres
sem_init(&full, 0, 0); // 0 datos
sem_init(&mutex, 0, 1); // binario: un lock
trace_start();
watchdog_start(1, "Deadlock: C duerme en full con el mutex.", NULL);
pthread_t p[NP], c[NC];
for (long i = 0; i < NC; i++)
pthread_create(&c[i], NULL, consumer, (void *)i);
for (long i = 0; i < NP; i++)
pthread_create(&p[i], NULL, producer, (void *)i);
for (int i = 0; i < NP; i++)
pthread_join(p[i], NULL);
for (int i = 0; i < NC; i++)
produce(-1); // un fin por consumidor
long total = 0;
for (int i = 0; i < NC; i++) {
pthread_join(c[i], NULL);
total += sums[i];
}
watchdog_done();
printf("consumidos %ld de %d: %s\n", total, NP * ITEMS,
total == NP * ITEMS ? "OK" : "ERROR");
return 0;
}// throttle.c — un semáforo hecho con un lock y una CV (un "Zemaphore")
// usado para limitar cuántos hilos entran a la vez a una región costosa.
//
// Uso: ./throttle T K
// T hilos quieren entrar; el Zemaphore empieza en K.
// Se mide el máximo de hilos que estuvieron dentro a la vez.
#include "trace.h"
typedef struct {
int value;
pthread_cond_t cond;
pthread_mutex_t lock;
} zem_t;
static void zem_init(zem_t *s, int value) {
s->value = value;
pthread_cond_init(&s->cond, NULL);
pthread_mutex_init(&s->lock, NULL);
}
static void zem_wait(zem_t *s) {
pthread_mutex_lock(&s->lock);
while (s->value <= 0) // nunca baja de 0
pthread_cond_wait(&s->cond, &s->lock);
s->value--;
pthread_mutex_unlock(&s->lock);
}
static void zem_post(zem_t *s) {
pthread_mutex_lock(&s->lock);
s->value++;
pthread_cond_signal(&s->cond);
pthread_mutex_unlock(&s->lock);
}
static zem_t throttle;
static atomic_int inside, max_inside;
static void *worker(void *arg) {
(void)arg;
for (int i = 0; i < 5; i++) {
zem_wait(&throttle);
int now = atomic_fetch_add(&inside, 1) + 1;
int old = atomic_load(&max_inside);
while (now > old && !atomic_compare_exchange_weak(&max_inside, &old, now))
;
msleep(5); // región costosa (p. ej., mucha memoria)
atomic_fetch_sub(&inside, 1);
zem_post(&throttle);
}
return NULL;
}
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "uso: %s T K\n", argv[0]);
return 1;
}
int nt = atoi(argv[1]), k = atoi(argv[2]);
pthread_t t[nt];
zem_init(&throttle, k);
trace_start();
for (int i = 0; i < nt; i++)
pthread_create(&t[i], NULL, worker, NULL);
for (int i = 0; i < nt; i++)
pthread_join(t[i], NULL);
printf("%d hilos, límite %d: máximo dentro a la vez = %d (%ld ms)\n",
nt, k, atomic_load(&max_inside), now_ms());
return 0;
}// trace.h — utilidades para las demostraciones de la semana 11.
//
// TRACE(...) imprime un evento con el tiempo (ms) desde trace_start().
// progress_bump() cada hilo avisa que avanzó.
// watchdog_start() un hilo vigila: si nadie avanza en `secs` segundos,
// imprime `msg` y termina el programa con código 0
// (el error es lo que se quería mostrar).
#ifndef TRACE_H
#define TRACE_H
#include <pthread.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
static struct timespec trace_t0;
static atomic_long progress;
static atomic_int finished;
static int watch_secs;
static const char *watch_msg;
static void (*watch_report)(void);
static inline void trace_start(void) {
clock_gettime(CLOCK_MONOTONIC, &trace_t0);
}
static inline long now_ms(void) {
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return (t.tv_sec - trace_t0.tv_sec) * 1000 +
(t.tv_nsec - trace_t0.tv_nsec) / 1000000;
}
#define TRACE(...) \
do { \
flockfile(stdout); \
printf("[%4ld ms] ", now_ms()); \
printf(__VA_ARGS__); \
printf("\n"); \
fflush(stdout); \
funlockfile(stdout); \
} while (0)
static inline void msleep(int ms) { usleep(ms * 1000); }
static inline void progress_bump(void) { atomic_fetch_add(&progress, 1); }
static inline void *watchdog(void *arg) {
(void)arg;
long last = atomic_load(&progress);
int quiet = 0; // décimas de segundo sin avance
while (!atomic_load(&finished)) {
msleep(100);
long p = atomic_load(&progress);
if (p != last) {
last = p;
quiet = 0;
} else if (++quiet >= watch_secs * 10) {
if (atomic_load(&finished))
break;
TRACE("WATCHDOG: %d s sin avance.", watch_secs);
printf(" %s\n", watch_msg);
if (watch_report)
watch_report();
exit(0); // mata a los hilos dormidos
}
}
return NULL;
}
static inline void watchdog_start(int secs, const char *msg,
void (*report)(void)) {
pthread_t w;
watch_secs = secs;
watch_msg = msg;
watch_report = report;
pthread_create(&w, NULL, watchdog, NULL);
pthread_detach(w);
}
static inline void watchdog_done(void) { atomic_store(&finished, 1); }
#endifErrores
Ejemplos con errores a propósito: no compilan, fallan o dan un resultado incorrecto. Los apuntes explican cada uno.
// pcbug.c — los dos errores del productor/consumidor, forzados paso a paso.
//
// Buffer de UN elemento, un productor (Tp) y dos consumidores (Tc1, Tc2).
//
// ./pcbug if if + una CV: Tc2 se cuela y Tc1 lee el buffer vacío
// ./pcbug while la misma intercalación con while: Tc1 vuelve a revisar
// ./pcbug onecv while + una CV: Tc1 despierta a Tc2 en vez de a Tp
// y los tres terminan dormidos
// ./pcbug twocv while + dos CVs (empty/fill): siempre funciona
//
// Los msleep() fuerzan el orden de llegada al mutex. Un watchdog detecta
// cuando nadie avanza.
#include <string.h>
#include "trace.h"
static int buffer, count = 0; // buffer de un elemento
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // la única CV
static pthread_cond_t empty = PTHREAD_COND_INITIALIZER; // solo en twocv
static pthread_cond_t fill = PTHREAD_COND_INITIALIZER; // solo en twocv
static int use_if, two_cvs;
static atomic_int bug;
static void put(int v) { buffer = v; count = 1; }
static int get(const char *who) {
if (count == 0) { // en OSTEP: assert(count == 1)
TRACE("%s: get() con el buffer VACÍO: ¡error!", who);
bug = 1;
return -1;
}
count = 0;
return buffer;
}
// Espera mientras (o si) se cumple la condición `cond_expr`.
#define WAIT_WHILE(cond_expr, cv, who, what) \
do { \
if (use_if) { \
if (cond_expr) { \
TRACE("%s: %s, wait", who, what); \
pthread_cond_wait(cv, &mutex); \
TRACE("%s: despierta con el lock", who); \
} \
} else { \
while (cond_expr) { \
TRACE("%s: %s, wait", who, what); \
pthread_cond_wait(cv, &mutex); \
TRACE("%s: despierta con el lock", who); \
} \
} \
} while (0)
// ---------- Escenario 1 (if / while): Tc2 se cuela ----------
static void *s1_tc1(void *arg) {
(void)arg;
pthread_mutex_lock(&mutex);
WAIT_WHILE(count == 0, &cond, "Tc1", "buffer vacío");
int v = get("Tc1");
if (v != -1)
TRACE("Tc1: consume %d", v);
pthread_mutex_unlock(&mutex);
progress_bump();
return NULL;
}
static void *s1_tc2(void *arg) {
(void)arg;
msleep(20); // llega mientras Tp tiene el lock
TRACE("Tc2: pide el lock");
pthread_mutex_lock(&mutex);
WAIT_WHILE(count == 0, &cond, "Tc2", "buffer vacío");
TRACE("Tc2: consume %d (se coló)", get("Tc2"));
pthread_mutex_unlock(&mutex);
progress_bump();
return NULL;
}
static void *s1_tp(void *arg) {
(void)arg;
for (int i = 1; i <= 2; i++) {
msleep(i == 1 ? 10 : 100);
pthread_mutex_lock(&mutex);
WAIT_WHILE(count == 1, &cond, "Tp ", "buffer lleno");
if (i == 1)
msleep(20); // Tc2 se forma en el mutex
put(i);
TRACE("Tp : put(%d), signal", i);
pthread_cond_signal(&cond);
if (i == 1)
msleep(20); // Tc1 despierta y se forma detrás
pthread_mutex_unlock(&mutex);
progress_bump();
}
return NULL;
}
// ---------- Escenario 2 (onecv / twocv): todos duermen ----------
// Tp mete 1, 2 y luego un -1 por consumidor. Los consumidores repiten.
static void *s2_consumer(void *arg) {
const char *who = arg;
pthread_cond_t *wait_cv = two_cvs ? &fill : &cond;
pthread_cond_t *sig_cv = two_cvs ? &empty : &cond;
if (who[2] == '2')
msleep(5); // Tc1 duerme primero, luego Tc2
for (;;) {
pthread_mutex_lock(&mutex);
WAIT_WHILE(count == 0, wait_cv, who, "buffer vacío");
int v = get(who);
TRACE("%s: consume %d, signal", who, v);
pthread_cond_signal(sig_cv);
pthread_mutex_unlock(&mutex);
progress_bump();
if (v == -1)
return NULL;
}
}
static void *s2_tp(void *arg) {
(void)arg;
int items[] = {1, 2, -1, -1};
pthread_cond_t *wait_cv = two_cvs ? &empty : &cond;
pthread_cond_t *sig_cv = two_cvs ? &fill : &cond;
msleep(20);
for (int i = 0; i < 4; i++) {
pthread_mutex_lock(&mutex);
WAIT_WHILE(count == 1, wait_cv, "Tp ", "buffer lleno");
put(items[i]);
TRACE("Tp : put(%d), signal", items[i]);
pthread_cond_signal(sig_cv);
pthread_mutex_unlock(&mutex);
progress_bump();
}
return NULL;
}
int main(int argc, char *argv[]) {
const char *mode = argc > 1 ? argv[1] : "";
int scenario;
if (strcmp(mode, "if") == 0) {
scenario = 1; use_if = 1;
} else if (strcmp(mode, "while") == 0) {
scenario = 1;
} else if (strcmp(mode, "onecv") == 0) {
scenario = 2;
} else if (strcmp(mode, "twocv") == 0) {
scenario = 2; two_cvs = 1;
} else {
fprintf(stderr, "uso: %s if|while|onecv|twocv\n", argv[0]);
return 1;
}
trace_start();
watchdog_start(1, "Tp, Tc1 y Tc2 duermen: nadie los despertará.", NULL);
pthread_t tp, tc1, tc2;
if (scenario == 1) {
pthread_create(&tc1, NULL, s1_tc1, NULL);
pthread_create(&tc2, NULL, s1_tc2, NULL);
pthread_create(&tp, NULL, s1_tp, NULL);
} else {
pthread_create(&tc1, NULL, s2_consumer, "Tc1");
pthread_create(&tc2, NULL, s2_consumer, "Tc2");
pthread_create(&tp, NULL, s2_tp, NULL);
}
pthread_join(tc1, NULL);
pthread_join(tc2, NULL);
if (scenario == 1 && bug) { // nadie consumirá el 2: no esperar a Tp
watchdog_done();
printf("resultado: ERROR, un consumidor leyó un buffer vacío\n");
return 0;
}
pthread_join(tp, NULL);
watchdog_done();
printf("resultado: %s\n", bug ? "ERROR" : "OK");
return 0;
}Compilación
Cómo se compilan y ejecutan los ejemplos.
# Semana 11: variables de condición y semáforos.
# make compila todo
# make test corre cada demostración (unos 15 s en total)
# prioinv necesita CAP_SYS_NICE (SCHED_FIFO); sin él, solo avisa
# make clean borra los binarios
CFLAGS = -Wall -Wextra -g -pthread
LDLIBS = -pthread
PROGS = join pc pcbug sempc rwlock philo throttle barrier priosim prioinv
# Las soluciones aún no publicadas (code/.ocultos) no están en el repositorio:
# se omiten los programas sin fuente y sus líneas en las pruebas.
PROGS := $(foreach p,$(PROGS),$(if $(wildcard $(p).c),$(p)))
RUN = $(if $(wildcard $(1).c),./$(1),@: sin $(1).c)
all: $(PROGS)
$(PROGS): %: %.c trace.h
$(CC) $(CFLAGS) -o $@ $< $(LDLIBS)
test: $(PROGS)
./join ok
./join nostate
./join nolock
./pc 4 2 3 100000
./pcbug if
./pcbug while
./pcbug onecv
./pcbug twocv
./sempc ok
./sempc outside
$(call RUN,rwlock) 1
$(call RUN,rwlock) 8
./philo naive
./philo ordered
./throttle 8 2
$(call RUN,barrier) 4 3
./priosim none
./priosim inherit
./priosim ceiling
./prioinv none
./prioinv inherit
clean:
rm -f $(PROGS)
.PHONY: all test clean